diff --git a/.changelog.yml b/.changelog.yml index 657dfa1c0e..bfdee0c0ca 100644 --- a/.changelog.yml +++ b/.changelog.yml @@ -13,46 +13,42 @@ groups: - name: BREAKING labels: - - kind/breaking + - pr/breaking - name: SECURITY labels: - - kind/security + - topic/security - name: FEATURES labels: - - kind/feature + - type/feature - name: API labels: - - kind/api + - modifies/api - name: ENHANCEMENTS labels: - - kind/enhancement - - kind/refactor - - kind/ui + - type/enhancement + - type/refactoring + - topic/ui - name: BUGFIXES labels: - - kind/bug + - type/bug - name: TESTING labels: - - kind/testing - - - name: TRANSLATION - labels: - - kind/translation + - type/testing - name: BUILD labels: - - kind/build - - kind/lint + - topic/build + - topic/code-linting - name: DOCS labels: - - kind/docs + - type/docs - name: MISC default: true diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 3084351770..0000000000 --- a/.drone.yml +++ /dev/null @@ -1,425 +0,0 @@ ---- -kind: pipeline -name: release-version - -platform: - os: linux - arch: amd64 - -workspace: - base: /source - path: / - -trigger: - event: - - tag - -volumes: - - name: deps - temp: {} - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: deps-frontend - image: node:20 - pull: always - commands: - - make deps-frontend - - - name: deps-backend - image: gitea/test_env:linux-1.20-amd64 - pull: always - commands: - - make deps-backend - volumes: - - name: deps - path: /go - - - name: static - image: techknowlogick/xgo:go-1.21.x - pull: always - commands: - - curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt-get -qqy install nodejs - - export PATH=$PATH:$GOPATH/bin - - make release - environment: - GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not - TAGS: bindata sqlite sqlite_unlock_notify - DEBIAN_FRONTEND: noninteractive - depends_on: [fetch-tags] - volumes: - - name: deps - path: /go - - - name: gpg-sign - image: plugins/gpgsign:1 - pull: always - settings: - detach_sign: true - excludes: - - "dist/release/*.sha256" - files: - - "dist/release/*" - environment: - GPGSIGN_KEY: - from_secret: gpgsign_key - GPGSIGN_PASSPHRASE: - from_secret: gpgsign_passphrase - depends_on: [static] - - - name: release-tag - image: woodpeckerci/plugin-s3:latest - pull: always - settings: - acl: - from_secret: aws_s3_acl - region: - from_secret: aws_s3_region - bucket: - from_secret: aws_s3_bucket - endpoint: - from_secret: aws_s3_endpoint - path_style: - from_secret: aws_s3_path_style - source: "dist/release/*" - strip_prefix: dist/release/ - target: "/gitea/${DRONE_TAG##v}" - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: [gpg-sign] - - - name: github - image: plugins/github-release:latest - pull: always - settings: - files: - - "dist/release/*" - file_exists: overwrite - environment: - GITHUB_TOKEN: - from_secret: github_token - depends_on: [gpg-sign] - ---- -kind: pipeline -type: docker -name: docker-linux-amd64-release-version - -platform: - os: linux - arch: amd64 - -trigger: - ref: - include: - - "refs/tags/**" - exclude: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - auto_tag: true - auto_tag_suffix: linux-amd64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - auto_tag: true - auto_tag_suffix: linux-amd64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request ---- - -kind: pipeline -type: docker -name: docker-linux-amd64-release-candidate-version - -platform: - os: linux - arch: amd64 - -trigger: - ref: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - tags: ${DRONE_TAG##v}-linux-amd64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - tags: ${DRONE_TAG##v}-linux-amd64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - ---- -kind: pipeline -type: docker -name: docker-linux-arm64-release-version - -platform: - os: linux - arch: arm64 - -trigger: - ref: - include: - - "refs/tags/**" - exclude: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - auto_tag: true - auto_tag_suffix: linux-arm64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - auto_tag: true - auto_tag_suffix: linux-arm64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - ---- -kind: pipeline -type: docker -name: docker-linux-arm64-release-candidate-version - -platform: - os: linux - arch: arm64 - -trigger: - ref: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - tags: ${DRONE_TAG##v}-linux-arm64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - tags: ${DRONE_TAG##v}-linux-arm64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - ---- -kind: pipeline -type: docker -name: docker-manifest-version - -platform: - os: linux - arch: amd64 - -steps: - - name: manifest-rootless - image: plugins/manifest - pull: always - settings: - auto_tag: true - ignore_missing: true - spec: docker/manifest.rootless.tmpl - password: - from_secret: docker_password - username: - from_secret: docker_username - - - name: manifest - image: plugins/manifest - settings: - auto_tag: true - ignore_missing: true - spec: docker/manifest.tmpl - password: - from_secret: docker_password - username: - from_secret: docker_username - -trigger: - ref: - - "refs/tags/**" - paths: - exclude: - - "docs/**" - -depends_on: - - docker-linux-amd64-release-version - - docker-linux-amd64-release-candidate-version - - docker-linux-arm64-release-version - - docker-linux-arm64-release-candidate-version diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 846823abc7..139cbf9109 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -11,14 +11,15 @@ parserOptions: plugins: - "@eslint-community/eslint-plugin-eslint-comments" - eslint-plugin-array-func - - eslint-plugin-custom-elements - - eslint-plugin-import + - eslint-plugin-i - eslint-plugin-jquery - 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: @@ -41,11 +42,67 @@ overrides: 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: ["build/generate-images.js"] rules: - import/no-unresolved: [0] - import/no-extraneous-dependencies: [0] + i/no-unresolved: [0] + i/no-extraneous-dependencies: [0] - files: ["*.config.*"] rules: - import/no-unused-modules: [0] + i/no-unused-modules: [0] + - files: ["**/*.test.*", "web_src/js/test/setup.js"] + 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.js", "web_src/js/standalone/**/*"] + rules: + no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression] rules: "@eslint-community/eslint-comments/disable-enable-pair": [2] @@ -85,19 +142,6 @@ rules: consistent-this: [0] constructor-super: [2] curly: [0] - custom-elements/expose-class-on-global: [0] - custom-elements/extends-correct-class: [2] - custom-elements/file-name-matches-element: [2] - custom-elements/no-constructor: [2] - custom-elements/no-customized-built-in-elements: [2] - custom-elements/no-dom-traversal-in-attributechangedcallback: [2] - custom-elements/no-dom-traversal-in-connectedcallback: [2] - custom-elements/no-exports-with-element: [2] - custom-elements/no-method-prefixed-with-on: [2] - custom-elements/no-unchecked-define: [0] - custom-elements/one-element-per-file: [0] - custom-elements/tag-name-matches-class: [2] - custom-elements/valid-tag-name: [2] default-case-last: [2] default-case: [0] default-param-last: [0] @@ -120,49 +164,49 @@ rules: id-length: [0] id-match: [0] implicit-arrow-linebreak: [0] - import/consistent-type-specifier-style: [0] - import/default: [0] - import/dynamic-import-chunkname: [0] - import/export: [2] - import/exports-last: [0] - import/extensions: [2, always, {ignorePackages: true}] - import/first: [2] - import/group-exports: [0] - import/max-dependencies: [0] - import/named: [2] - import/namespace: [0] - import/newline-after-import: [0] - import/no-absolute-path: [0] - import/no-amd: [2] - import/no-anonymous-default-export: [0] - import/no-commonjs: [2] - import/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] - import/no-default-export: [0] - import/no-deprecated: [0] - import/no-dynamic-require: [0] - import/no-empty-named-blocks: [2] - import/no-extraneous-dependencies: [2] - import/no-import-module-exports: [0] - import/no-internal-modules: [0] - import/no-mutable-exports: [0] - import/no-named-as-default-member: [0] - import/no-named-as-default: [2] - import/no-named-default: [0] - import/no-named-export: [0] - import/no-namespace: [0] - import/no-nodejs-modules: [0] - import/no-relative-packages: [0] - import/no-relative-parent-imports: [0] - import/no-restricted-paths: [0] - import/no-self-import: [2] - import/no-unassigned-import: [0] - import/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$", ^vitest/]}] - import/no-unused-modules: [2, {unusedExports: true}] - import/no-useless-path-segments: [2, {commonjs: true}] - import/no-webpack-loader-syntax: [2] - import/order: [0] - import/prefer-default-export: [0] - import/unambiguous: [0] + i/consistent-type-specifier-style: [0] + i/default: [0] + i/dynamic-import-chunkname: [0] + i/export: [2] + i/exports-last: [0] + i/extensions: [2, always, {ignorePackages: true}] + i/first: [2] + i/group-exports: [0] + i/max-dependencies: [0] + i/named: [2] + i/namespace: [0] + i/newline-after-import: [0] + i/no-absolute-path: [0] + i/no-amd: [2] + i/no-anonymous-default-export: [0] + i/no-commonjs: [2] + i/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] + i/no-default-export: [0] + i/no-deprecated: [0] + i/no-dynamic-require: [0] + i/no-empty-named-blocks: [2] + i/no-extraneous-dependencies: [2] + i/no-import-module-exports: [0] + i/no-internal-modules: [0] + i/no-mutable-exports: [0] + i/no-named-as-default-member: [0] + i/no-named-as-default: [2] + i/no-named-default: [0] + i/no-named-export: [0] + i/no-namespace: [0] + i/no-nodejs-modules: [0] + i/no-relative-packages: [0] + i/no-relative-parent-imports: [0] + i/no-restricted-paths: [0] + i/no-self-import: [2] + i/no-unassigned-import: [0] + i/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$", ^vitest/]}] + i/no-unused-modules: [2, {unusedExports: true}] + i/no-useless-path-segments: [2, {commonjs: true}] + i/no-webpack-loader-syntax: [2] + i/order: [0] + i/prefer-default-export: [0] + i/unambiguous: [0] indent: [2, 2, {SwitchCase: 1}] init-declarations: [0] jquery/no-ajax-events: [2] @@ -420,7 +464,7 @@ rules: 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] + no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression, {selector: "CallExpression[callee.name='fetch']", message: "use modules/fetch.js instead"}] no-return-assign: [0] no-script-url: [2] no-self-assign: [2, {props: true}] @@ -483,7 +527,7 @@ rules: prefer-exponentiation-operator: [2] prefer-named-capture-group: [0] prefer-numeric-literals: [2] - prefer-object-has-own: [0] + prefer-object-has-own: [2] prefer-object-spread: [2] prefer-promise-reject-errors: [2, {allowEmptyReject: false}] prefer-regex-literals: [2] @@ -508,6 +552,7 @@ rules: 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] @@ -538,6 +583,8 @@ rules: 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] @@ -557,10 +604,12 @@ rules: 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] @@ -737,14 +786,27 @@ rules: valid-typeof: [2, {requireStringLiterals: true}] vars-on-top: [0] wc/attach-shadow-constructor: [2] + wc/define-tag-after-class-definition: [0] + wc/expose-class-on-global: [0] + wc/file-name-matches-element: [2] + wc/guard-define-call: [0] wc/guard-super-call: [2] + wc/max-elements-per-file: [0] + wc/no-child-traversal-in-attributechangedcallback: [2] + wc/no-child-traversal-in-connectedcallback: [2] wc/no-closed-shadow-root: [2] wc/no-constructor-attributes: [2] wc/no-constructor-params: [2] - wc/no-invalid-element-name: [0] # covered by custom-elements/valid-tag-name + wc/no-constructor: [2] + wc/no-customized-built-in-elements: [2] + wc/no-exports-with-element: [2] + wc/no-invalid-element-name: [2] + wc/no-invalid-extends: [2] + wc/no-method-prefixed-with-on: [2] wc/no-self-class: [2] wc/no-typos: [2] wc/require-listener-teardown: [2] + wc/tag-name-matches-class: [2] wrap-iife: [2, inside] wrap-regex: [0] yield-star-spacing: [2, after] diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index c482affbbd..94c1bd0ab7 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -1,6 +1,6 @@ name: Bug Report description: Found something you weren't expecting? Report it here! -labels: ["kind/bug"] +labels: ["type/bug"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml index 71aaa09423..3c9953019d 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yaml +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -1,6 +1,6 @@ name: Feature Request description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here! -labels: ["kind/proposal"] +labels: ["type/proposal"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/ui.bug-report.yaml b/.github/ISSUE_TEMPLATE/ui.bug-report.yaml index ef0a1014e5..387aee897b 100644 --- a/.github/ISSUE_TEMPLATE/ui.bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/ui.bug-report.yaml @@ -1,6 +1,6 @@ name: Web Interface Bug Report description: Something doesn't look quite as it should? Report it here! -labels: ["kind/bug", "kind/ui"] +labels: ["type/bug", "topic/ui"] body: - type: markdown attributes: diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000000..023fb05a29 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,5 @@ +self-hosted-runner: + labels: + - actuated-4cpu-8gb + - actuated-4cpu-16gb + - nscloud diff --git a/.github/labeler.yml b/.github/labeler.yml index ee6c7d6ae8..8a5ab26975 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,32 +1,36 @@ -kind/docs: +modifies/docs: - "**/*.md" - "docs/**" -kind/ui: +modifies/frontend: - "web_src/**/*" + +modifies/templates: - all: ["templates/**", "!templates/swagger/v1_json.tmpl"] -kind/api: - - "templates/swagger/v1_json.tmpl" +modifies/api: - "routers/api/**" + - "templates/swagger/v1_json.tmpl" -kind/build: +modifies/cli: + - "cmd/**" + +modifies/translation: + - "options/locale/*.ini" + +modifies/migrations: + - "models/migrations/**/*" + +modifies/internal: - "Makefile" - "Dockerfile" - "Dockerfile.rootless" - "docker/**" - "webpack.config.js" - -theme/package-registry: - - "modules/packages/**" - -kind/cli: - - "cmd/**" - -kind/lint: - ".eslintrc.yaml" - ".golangci.yml" - ".markdownlint.yaml" - ".spectral.yaml" - ".stylelintrc.yaml" - ".yamllint.yaml" + - ".github/**" diff --git a/.github/workflows/cron-licenses.yml b/.github/workflows/cron-licenses.yml index acdf7cd364..0fbcbf603d 100644 --- a/.github/workflows/cron-licenses.yml +++ b/.github/workflows/cron-licenses.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'go-gitea/gitea' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - run: make generate-license generate-gitignore timeout-minutes: 40 diff --git a/.github/workflows/cron-translations.yml b/.github/workflows/cron-translations.yml index 3f147c685d..b0effdee9d 100644 --- a/.github/workflows/cron-translations.yml +++ b/.github/workflows/cron-translations.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'go-gitea/gitea' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: download from crowdin uses: docker://jonasfranz/crowdin env: @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'go-gitea/gitea' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: push translations to crowdin uses: docker://jonasfranz/crowdin env: diff --git a/.github/workflows/disk-clean.yml b/.github/workflows/disk-clean.yml new file mode 100644 index 0000000000..78cd251354 --- /dev/null +++ b/.github/workflows/disk-clean.yml @@ -0,0 +1,40 @@ +name: disk-clean + +on: + workflow_call: + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # FIXME: https://github.com/jlumbroso/free-disk-space/issues/17 + - name: same as 'large-packages' but without 'google-cloud-sdk' + shell: bash + run: | + sudo apt-get update + sudo apt-get remove -y '^dotnet-.*' || true + sudo apt-get remove -y '^llvm-.*' || true + sudo apt-get remove -y 'php.*' || true + sudo apt-get remove -y '^mongodb-.*' || true + sudo apt-get remove -y '^mysql-.*' || true + sudo apt-get remove -y azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri || true + sudo apt-get autoremove -y + sudo apt-get clean + env: + DEBIAN_FRONTEND: noninteractive + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: false + docker-images: false + swap-storage: true diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index 48db7a732e..e7039053af 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -34,7 +34,7 @@ jobs: swagger: ${{ steps.changes.outputs.swagger }} yaml: ${{ steps.changes.outputs.yaml }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dorny/paths-filter@v2 id: changes with: @@ -64,9 +64,12 @@ jobs: - "**/*.md" - "docs/**" - ".markdownlint.yaml" + - "package.json" + - "package-lock.json" actions: - ".github/workflows/*" + - "Makefile" templates: - "templates/**/*.tmpl" @@ -90,3 +93,5 @@ jobs: - "**/*.yml" - "**/*.yaml" - ".yamllint.yaml" + - "pyproject.toml" + - "poetry.lock" diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index bcbd218846..bf9236b093 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -16,10 +16,10 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - run: make deps-backend deps-tools - run: make lint-backend @@ -31,7 +31,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.11" @@ -44,7 +44,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.11" @@ -57,7 +57,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 20 @@ -69,10 +69,10 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - run: make deps-backend deps-tools - run: make lint-go-windows lint-go-vet @@ -86,10 +86,10 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - run: make deps-backend deps-tools - run: make lint-go @@ -101,10 +101,10 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - run: make deps-backend deps-tools - run: make --always-make checks-backend # ensure the "go-licenses" make target runs @@ -114,7 +114,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 20 @@ -129,10 +129,10 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true # no frontend build here as backend should be able to build # even without any frontend files @@ -161,7 +161,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 20 @@ -174,6 +174,9 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + check-latest: true - run: make lint-actions diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index bbe589d5c8..97446e6cd3 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest services: pgsql: - image: postgres:15 + image: postgres:12 env: POSTGRES_DB: test POSTGRES_PASSWORD: postgres @@ -31,17 +31,17 @@ jobs: minio: # as github actions doesn't support "entrypoint", we need to use a non-official image # that has a custom entrypoint set to "minio server /data" - image: bitnami/minio:2021.3.17 + image: bitnami/minio:2023.8.31 env: - MINIO_ACCESS_KEY: 123456 - MINIO_SECRET_KEY: 12345678 + MINIO_ROOT_USER: 123456 + MINIO_ROOT_PASSWORD: 12345678 ports: - "9000:9000" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts' @@ -63,10 +63,10 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - run: make deps-backend - run: make backend @@ -85,13 +85,6 @@ jobs: needs: files-changed runs-on: ubuntu-latest services: - mysql: - image: mysql:5.7 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: true - MYSQL_DATABASE: test - ports: - - "3306:3306" elasticsearch: image: elasticsearch:7.5.0 env: @@ -104,13 +97,6 @@ jobs: MEILI_ENV: development # disable auth ports: - "7700:7700" - smtpimap: - image: tabascoterrier/docker-imap-devel:latest - ports: - - "25:25" - - "143:143" - - "587:587" - - "993:993" redis: image: redis options: >- # wait until redis has started @@ -128,10 +114,10 @@ jobs: ports: - "9000:9000" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts' @@ -152,16 +138,16 @@ jobs: RACE_ENABLED: true GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }} - test-mysql5: + test-mysql: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest services: mysql: - image: mysql:5.7 + image: mysql:8.0 env: MYSQL_ALLOW_EMPTY_PASSWORD: true - MYSQL_DATABASE: test + MYSQL_DATABASE: testgitea ports: - "3306:3306" elasticsearch: @@ -178,10 +164,10 @@ jobs: - "587:587" - "993:993" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts' @@ -197,43 +183,13 @@ jobs: USE_REPO_TEST_DIR: 1 TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200" - test-mysql8: - if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' - needs: files-changed - runs-on: ubuntu-latest - services: - mysql8: - image: mysql:8 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: true - MYSQL_DATABASE: testgitea - ports: - - "3306:3306" - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 - with: - go-version: "~1.21" - check-latest: true - - name: Add hosts to /etc/hosts - run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql8" | sudo tee -a /etc/hosts' - - run: make deps-backend - - run: make backend - env: - TAGS: bindata - - run: make test-mysql8-migration test-mysql8 - timeout-minutes: 50 - env: - TAGS: bindata - USE_REPO_TEST_DIR: 1 - test-mssql: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest services: mssql: - image: mcr.microsoft.com/mssql/server:latest + image: mcr.microsoft.com/mssql/server:2017-latest env: ACCEPT_EULA: Y MSSQL_PID: Standard @@ -241,10 +197,10 @@ jobs: ports: - "1433:1433" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql" | sudo tee -a /etc/hosts' diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml index 7b950bfd38..357ccb48be 100644 --- a/.github/workflows/pull-e2e-tests.yml +++ b/.github/workflows/pull-e2e-tests.yml @@ -16,10 +16,10 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - uses: actions/setup-node@v3 with: diff --git a/.github/workflows/pull-labeler.yml b/.github/workflows/pull-labeler.yml index c62142b9d2..edd2f6d16e 100644 --- a/.github/workflows/pull-labeler.yml +++ b/.github/workflows/pull-labeler.yml @@ -18,4 +18,3 @@ jobs: - uses: actions/labeler@v4 with: dot: true - sync-labels: true diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 8387f615d9..b70a65c070 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -1,20 +1,26 @@ -name: release-nightly-assets +name: release-nightly on: push: branches: [ main, release/v* ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + disk-clean: + uses: ./.github/workflows/disk-clean.yml nightly-binary: - runs-on: ubuntu-latest + runs-on: nscloud steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force - uses: actions/setup-go@v4 with: - go-version: "~1.21" + go-version-file: go.mod check-latest: true - uses: actions/setup-node@v3 with: @@ -42,22 +48,26 @@ jobs: REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" + - name: configure aws + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: upload binaries to s3 - uses: jakejarvis/s3-sync-action@master - env: - AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - SOURCE_DIR: dist/release - DEST_DIR: gitea/${{ steps.clean_name.outputs.branch }} - nightly-docker: + run: | + aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress + nightly-docker-rootful: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force + - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + check-latest: true - uses: docker/setup-qemu-action@v2 - uses: docker/setup-buildx-action@v2 - name: Get cleaned branch name @@ -75,6 +85,8 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: fetch go modules + run: make vendor - name: build rootful docker image uses: docker/build-push-action@v4 with: @@ -82,6 +94,36 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: gitea/gitea:${{ steps.clean_name.outputs.branch }} + nightly-docker-rootless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + check-latest: true + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - name: Get cleaned branch name + id: clean_name + run: | + # if main then say nightly otherwise cleanup name + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + echo "branch=nightly" >> "$GITHUB_OUTPUT" + exit 0 + fi + REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') + echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: fetch go modules + run: make vendor - name: build rootless docker image uses: docker/build-push-action@v4 with: diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml new file mode 100644 index 0000000000..06b21db4db --- /dev/null +++ b/.github/workflows/release-tag-rc.yml @@ -0,0 +1,125 @@ +name: release-tag-rc + +on: + push: + tags: + - 'v1*-rc*' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + binary: + runs-on: nscloud + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + check-latest: true + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: make deps-frontend deps-backend + # xgo build + - run: make release + env: + TAGS: bindata sqlite sqlite_unlock_notify + - name: import gpg key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPGSIGN_KEY }} + passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} + - name: sign binaries + run: | + for f in dist/release/*; do + echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f" + done + # clean branch name to get the folder name in S3 + - name: Get cleaned branch name + id: clean_name + run: | + REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') + echo "Cleaned name is ${REF_NAME}" + echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" + - name: configure aws + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: upload binaries to s3 + run: | + aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress + - name: create github release + run: | + gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/* + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + docker-rootful: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + # 1.2.3-rc0 + tags: | + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootful docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + docker-rootless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + # each tag below will have the suffix of -rootless + flavor: | + suffix=-rootless + # 1.2.3-rc0 + tags: | + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootless docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + file: Dockerfile.rootless + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml new file mode 100644 index 0000000000..f85f002894 --- /dev/null +++ b/.github/workflows/release-tag-version.yml @@ -0,0 +1,141 @@ +name: release-tag-version + +on: + push: + tags: + - 'v1.*' + - '!v1*-rc*' + - '!v1*-dev' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + binary: + runs-on: nscloud + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + check-latest: true + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: make deps-frontend deps-backend + # xgo build + - run: make release + env: + TAGS: bindata sqlite sqlite_unlock_notify + - name: import gpg key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPGSIGN_KEY }} + passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} + - name: sign binaries + run: | + for f in dist/release/*; do + echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f" + done + # clean branch name to get the folder name in S3 + - name: Get cleaned branch name + id: clean_name + run: | + REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') + echo "Cleaned name is ${REF_NAME}" + echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" + - name: configure aws + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: upload binaries to s3 + run: | + aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress + - name: create github release + run: | + gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/* + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + docker-rootful: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + # this will generate tags in the following format: + # latest + # 1 + # 1.2 + # 1.2.3 + tags: | + type=raw,value=latest + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootful docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + docker-rootless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + # each tag below will have the suffix of -rootless + flavor: | + suffix=-rootless + # this will generate tags in the following format (with -rootless suffix added): + # latest + # 1 + # 1.2 + # 1.2.3 + tags: | + type=raw,value=latest + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootless docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + file: Dockerfile.rootless + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index e3b9c3c43e..53365ed0b4 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ cpu.out /.go-licenses # Snapcraft +/gitea_a*.txt snap/.snapcraft/ parts/ stage/ diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 7ccdd53e89..0af8bcd6fe 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -6,7 +6,6 @@ line-length: {code_blocks: false, tables: false, stern: true, line_length: -1} no-alt-text: false no-bare-urls: false no-blanks-blockquote: false -no-duplicate-header: {allow_different_nesting: true} no-emphasis-as-header: false no-empty-links: false no-hard-tabs: {code_blocks: false} diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml index f30d79d71f..f889a6867c 100644 --- a/.stylelintrc.yaml +++ b/.stylelintrc.yaml @@ -103,7 +103,7 @@ rules: property-no-vendor-prefix: null rule-empty-line-before: null rule-selector-property-disallowed-list: null - scale-unlimited/declaration-strict-value: [[color, background-color, border-color, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true}] + scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}] selector-attribute-name-disallowed-list: null selector-attribute-operator-allowed-list: null selector-attribute-operator-disallowed-list: null diff --git a/.yamllint.yaml b/.yamllint.yaml index c0fce7c301..5a1e1e8751 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -24,8 +24,6 @@ rules: document-start: level: error present: false - ignore: | - /.drone.yml document-end: present: false @@ -44,5 +42,3 @@ rules: ignore: | .venv node_modules - /models/fixtures - /models/migrations/fixtures diff --git a/BSDmakefile b/BSDmakefile index 13174f8fd2..79696eadcf 100644 --- a/BSDmakefile +++ b/BSDmakefile @@ -42,13 +42,13 @@ GARGS = "--no-print-directory" # The GNU convention is to use the lowercased `prefix` variable/macro to # specify the installation directory. Humor them. -GPREFIX = "" +GPREFIX = .if defined(PREFIX) && ! defined(prefix) GPREFIX = 'prefix = "$(PREFIX)"' .endif .BEGIN: .SILENT - which $(GMAKE) || printf "Error: GNU Make is required!\n\n" 1>&2 && false + which $(GMAKE) || (printf "Error: GNU Make is required!\n\n" 1>&2 && false) .PHONY: FRC $(.TARGETS): FRC diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef677b88c..ea0f80c0c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,33 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.com). +## [1.20.5](https://github.com/go-gitea/gitea/releases/tag/v1.20.5) - 2023-10-03 + +* ENHANCEMENTS + * Fix z-index on markdown completion (#27237) (#27242 & #27238) + * Use secure cookie for HTTPS sites (#26999) (#27013) +* BUGFIXES + * Fix git 2.11 error when checking IsEmpty (#27393) (#27396) + * Allow get release download files and lfs files with oauth2 token format (#26430) (#27378) + * Fix orphan check for deleted branch (#27310) (#27320) + * Quote table `release` in sql queries (#27205) (#27219) + * Fix release URL in webhooks (#27182) (#27184) + * Fix successful return value for `SyncAndGetUserSpecificDiff` (#27152) (#27156) + * fix pagination for followers and following (#27127) (#27138) + * Fix issue templates when blank isses are disabled (#27061) (#27082) + * Fix context cache bug & enable context cache for dashabord commits' authors(#26991) (#27017) + * Fix INI parsing for value with trailing slash (#26995) (#27001) + * Fix PushEvent NullPointerException jenkinsci/github-plugin (#27203) (#27249) + * Fix organization field being null in POST /orgs/{orgid}/teams (#27150) (#27167 & #27162) + * Fix bug of review request number (#27406) (#27104) +* TESTING + * services/wiki: Close() after error handling (#27129) (#27137) +* DOCS + * Improve actions docs related to `pull_request` event (#27126) (#27145) +* MISC + * Add logs for data broken of comment review (#27326) (#27344) + * Load reviewer before sending notification (#27063) (#27064) + ## [1.20.4](https://github.com/go-gitea/gitea/releases/tag/v1.20.4) - 2023-09-08 * SECURITY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cd83a4898..75d2cd22e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -225,17 +225,20 @@ PRs without a milestone may not be merged. ### Labels -Every PR should be labeled correctly with every label that applies. \ -This includes especially the distinction between `bug` (fixing existing functionality), `feature` (new functionality), `enhancement` (upgrades for existing functionality), and `refactoring` (improving the internal code structure without changing the output (much)). \ -Furthermore, +Almost all labels used inside Gitea can be classified as one of the following: + +- `modifies/…`: Determines which parts of the codebase are affected. These labels will be set through the CI. +- `topic/…`: Determines the conceptual component of Gitea that is affected, i.e. issues, projects, or authentication. At best, PRs should only target one component but there might be overlap. Must be set manually. +- `type/…`: Determines the type of an issue or PR (feature, refactoring, docs, bug, …). If GitHub supported scoped labels, these labels would be exclusive, so you should set **exactly** one, not more or less (every PR should fall into one of the provided categories, and only one). +- `issue/…` / `pr/…`: Labels that are specific to issues or PRs respectively and that are only necessary in a given context, i.e. `issue/not-a-bug` or `pr/need-2-approvals` + +Every PR should be labeled correctly with every label that applies. + +There are also some labels that will be managed automatically.\ +In particular, these are - the amount of pending required approvals -- whether this PR is `blocked`, a `backport` or `breaking` -- if it targets the `ui` or `api` -- if it increases the application `speed` -- reduces `memory usage` - -are oftentimes notable labels. +- has all `backport`s or needs a manual backport ### Breaking PRs diff --git a/MAINTAINERS b/MAINTAINERS index ce35a2f420..72171f80ed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -56,3 +56,6 @@ Denys Konovalov (@denyskon) Punit Inani (@puni9869) CaiCandong <1290147055@qq.com> (@caicandong) Rui Chen (@chenrui333) +Nanguan Lin (@lng2020) +kerwin612 (@kerwin612) +Gary Wang (@BLumia) diff --git a/Makefile b/Makefile index fd852cbaf9..8e03a71b8d 100644 --- a/Makefile +++ b/Makefile @@ -49,10 +49,14 @@ ifeq ($(HAS_GO), yes) CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS) endif -ifeq ($(OS), Windows_NT) - GOFLAGS := -v -buildmode=exe - EXECUTABLE ?= gitea.exe -else ifeq ($(OS), Windows) +ifeq ($(GOOS),windows) + IS_WINDOWS := yes +else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows) + ifeq ($(GOOS),) + IS_WINDOWS := yes + endif +endif +ifeq ($(IS_WINDOWS),yes) GOFLAGS := -v -buildmode=exe EXECUTABLE ?= gitea.exe else @@ -167,10 +171,6 @@ TEST_MYSQL_HOST ?= mysql:3306 TEST_MYSQL_DBNAME ?= testgitea TEST_MYSQL_USERNAME ?= root TEST_MYSQL_PASSWORD ?= -TEST_MYSQL8_HOST ?= mysql8:3306 -TEST_MYSQL8_DBNAME ?= testgitea -TEST_MYSQL8_USERNAME ?= root -TEST_MYSQL8_PASSWORD ?= TEST_PGSQL_HOST ?= pgsql:5432 TEST_PGSQL_DBNAME ?= testgitea TEST_PGSQL_USERNAME ?= postgres @@ -226,6 +226,7 @@ help: @echo " - test-frontend test frontend files" @echo " - test-backend test backend files" @echo " - test-e2e[\#TestSpecificName] test end to end using playwright" + @echo " - update update js and py dependencies" @echo " - update-js update js dependencies" @echo " - update-py update py dependencies" @echo " - webpack build webpack files" @@ -277,16 +278,15 @@ clean-all: clean .PHONY: clean clean: - $(GO) clean -i ./... rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \ integrations*.test \ e2e*.test \ - tests/integration/gitea-integration-pgsql/ tests/integration/gitea-integration-mysql/ tests/integration/gitea-integration-mysql8/ tests/integration/gitea-integration-sqlite/ \ - tests/integration/gitea-integration-mssql/ tests/integration/indexers-mysql/ tests/integration/indexers-mysql8/ tests/integration/indexers-pgsql tests/integration/indexers-sqlite \ - tests/integration/indexers-mssql tests/mysql.ini tests/mysql8.ini tests/pgsql.ini tests/mssql.ini man/ \ - tests/e2e/gitea-e2e-pgsql/ tests/e2e/gitea-e2e-mysql/ tests/e2e/gitea-e2e-mysql8/ tests/e2e/gitea-e2e-sqlite/ \ - tests/e2e/gitea-e2e-mssql/ tests/e2e/indexers-mysql/ tests/e2e/indexers-mysql8/ tests/e2e/indexers-pgsql/ tests/e2e/indexers-sqlite/ \ - tests/e2e/indexers-mssql/ tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ + tests/integration/gitea-integration-* \ + tests/integration/indexers-* \ + tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \ + tests/e2e/gitea-e2e-*/ \ + tests/e2e/indexers-*/ \ + tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ .PHONY: fmt fmt: @@ -550,27 +550,6 @@ test-mysql\#%: integrations.mysql.test generate-ini-mysql .PHONY: test-mysql-migration test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test -generate-ini-mysql8: - sed -e 's|{{TEST_MYSQL8_HOST}}|${TEST_MYSQL8_HOST}|g' \ - -e 's|{{TEST_MYSQL8_DBNAME}}|${TEST_MYSQL8_DBNAME}|g' \ - -e 's|{{TEST_MYSQL8_USERNAME}}|${TEST_MYSQL8_USERNAME}|g' \ - -e 's|{{TEST_MYSQL8_PASSWORD}}|${TEST_MYSQL8_PASSWORD}|g' \ - -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ - -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ - -e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \ - tests/mysql8.ini.tmpl > tests/mysql8.ini - -.PHONY: test-mysql8 -test-mysql8: integrations.mysql8.test generate-ini-mysql8 - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./integrations.mysql8.test - -.PHONY: test-mysql8\#% -test-mysql8\#%: integrations.mysql8.test generate-ini-mysql8 - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./integrations.mysql8.test -test.run $(subst .,/,$*) - -.PHONY: test-mysql8-migration -test-mysql8-migration: migrations.mysql8.test migrations.individual.mysql8.test - generate-ini-pgsql: sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \ -e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \ @@ -643,14 +622,6 @@ test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e/$* -.PHONY: test-e2e-mysql8 -test-e2e-mysql8: playwright e2e.mysql8.test generate-ini-mysql8 - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./e2e.mysql8.test - -.PHONY: test-e2e-mysql8\#% -test-e2e-mysql8\#%: playwright e2e.mysql8.test generate-ini-mysql8 - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./e2e.mysql8.test -test.run TestE2e/$* - .PHONY: test-e2e-pgsql test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test @@ -694,9 +665,6 @@ integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sq integrations.mysql.test: git-check $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test -integrations.mysql8.test: git-check $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql8.test - integrations.pgsql.test: git-check $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test @@ -717,11 +685,6 @@ migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test -.PHONY: migrations.mysql8.test -migrations.mysql8.test: $(GO_SOURCES) generate-ini-mysql8 - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql8.test - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./migrations.mysql8.test - .PHONY: migrations.pgsql.test migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test @@ -743,13 +706,7 @@ migrations.individual.mysql.test: $(GO_SOURCES) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \ done -.PHONY: migrations.individual.mysql8.test -migrations.individual.mysql8.test: $(GO_SOURCES) - for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \ - done - -.PHONY: migrations.individual.mysql8.test\#% +.PHONY: migrations.individual.sqlite.test\#% migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* @@ -787,9 +744,6 @@ migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite e2e.mysql.test: $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test -e2e.mysql8.test: $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql8.test - e2e.pgsql.test: $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test @@ -924,6 +878,9 @@ node_modules: package-lock.json poetry install @touch .venv +.PHONY: update +update: update-js update-py + .PHONY: update-js update-js: node-check | node_modules npx updates -u -f package.json diff --git a/README.md b/README.md index adf94dcc9a..aac793efe8 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@

Gitea - Git with a cup of tea

- - + + diff --git a/README_ZH.md b/README_ZH.md index 866b85e999..dd77c05de8 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,8 +6,8 @@

Gitea - Git with a cup of tea

- - + + diff --git a/SECURITY.md b/SECURITY.md index 5bee4a7f39..4c7d437844 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ Please **DO NOT** file a public issue, instead send your report privately to `se ## Protecting Security Information -Due to the sensitive nature of security information, you can use below GPG public key encrypt your mail body. +Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body. The PGP key is valid until June 24, 2024. diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 8a17148e1b..ee1e503f2a 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -289,6 +289,11 @@ "path": "github.com/cpuguy83/go-md2man/v2/md2man/LICENSE.md", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Brian Goff\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, + { + "name": "github.com/cyphar/filepath-securejoin", + "path": "github.com/cyphar/filepath-securejoin/LICENSE", + "licenseText": "Copyright (C) 2014-2015 Docker Inc \u0026 Go Authors. All rights reserved.\nCopyright (C) 2017 SUSE LLC. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, { "name": "github.com/davecgh/go-spew/spew", "path": "github.com/davecgh/go-spew/spew/LICENSE", @@ -589,21 +594,11 @@ "path": "github.com/gorilla/sessions/LICENSE", "licenseText": "Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n\t * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\t * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\t * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, - { - "name": "github.com/hashicorp/errwrap", - "path": "github.com/hashicorp/errwrap/LICENSE", - "licenseText": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n means each individual or legal entity that creates, contributes to the\n creation of, or owns Covered Software.\n\n1.2. “Contributor Version”\n\n means the combination of the Contributions of others (if any) used by a\n Contributor and that particular Contributor’s Contribution.\n\n1.3. “Contribution”\n\n means Covered Software of a particular Contributor.\n\n1.4. “Covered Software”\n\n means Source Code Form to which the initial Contributor has attached the\n notice in Exhibit A, the Executable Form of such Source Code Form, and\n Modifications of such Source Code Form, in each case including portions\n thereof.\n\n1.5. “Incompatible With Secondary Licenses”\n means\n\n a. that the initial Contributor has attached the notice described in\n Exhibit B to the Covered Software; or\n\n b. that the Covered Software was made available under the terms of version\n 1.1 or earlier of the License, but not also under the terms of a\n Secondary License.\n\n1.6. “Executable Form”\n\n means any form of the work other than Source Code Form.\n\n1.7. “Larger Work”\n\n means a work that combines Covered Software with other material, in a separate\n file or files, that is not Covered Software.\n\n1.8. “License”\n\n means this document.\n\n1.9. “Licensable”\n\n means having the right to grant, to the maximum extent possible, whether at the\n time of the initial grant or subsequently, any and all of the rights conveyed by\n this License.\n\n1.10. “Modifications”\n\n means any of the following:\n\n a. any file in Source Code Form that results from an addition to, deletion\n from, or modification of the contents of Covered Software; or\n\n b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. “Patent Claims” of a Contributor\n\n means any patent claim(s), including without limitation, method, process,\n and apparatus claims, in any patent Licensable by such Contributor that\n would be infringed, but for the grant of the License, by the making,\n using, selling, offering for sale, having made, import, or transfer of\n either its Contributions or its Contributor Version.\n\n1.12. “Secondary License”\n\n means either the GNU General Public License, Version 2.0, the GNU Lesser\n General Public License, Version 2.1, the GNU Affero General Public\n License, Version 3.0, or any later versions of those licenses.\n\n1.13. “Source Code Form”\n\n means the form of the work preferred for making modifications.\n\n1.14. “You” (or “Your”)\n\n means an individual or a legal entity exercising rights under this\n License. For legal entities, “You” includes any entity that controls, is\n controlled by, or is under common control with You. For purposes of this\n definition, “control” means (a) the power, direct or indirect, to cause\n the direction or management of such entity, whether by contract or\n otherwise, or (b) ownership of more than fifty percent (50%) of the\n outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n Each Contributor hereby grants You a world-wide, royalty-free,\n non-exclusive license:\n\n a. under intellectual property rights (other than patent or trademark)\n Licensable by such Contributor to use, reproduce, make available,\n modify, display, perform, distribute, and otherwise exploit its\n Contributions, either on an unmodified basis, with Modifications, or as\n part of a Larger Work; and\n\n b. under Patent Claims of such Contributor to make, use, sell, offer for\n sale, have made, import, and otherwise transfer either its Contributions\n or its Contributor Version.\n\n2.2. Effective Date\n\n The licenses granted in Section 2.1 with respect to any Contribution become\n effective for each Contribution on the date the Contributor first distributes\n such Contribution.\n\n2.3. Limitations on Grant Scope\n\n The licenses granted in this Section 2 are the only rights granted under this\n License. No additional rights or licenses will be implied from the distribution\n or licensing of Covered Software under this License. Notwithstanding Section\n 2.1(b) above, no patent license is granted by a Contributor:\n\n a. for any code that a Contributor has removed from Covered Software; or\n\n b. for infringements caused by: (i) Your and any other third party’s\n modifications of Covered Software, or (ii) the combination of its\n Contributions with other software (except as part of its Contributor\n Version); or\n\n c. under Patent Claims infringed by Covered Software in the absence of its\n Contributions.\n\n This License does not grant any rights in the trademarks, service marks, or\n logos of any Contributor (except as may be necessary to comply with the\n notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n No Contributor makes additional grants as a result of Your choice to\n distribute the Covered Software under a subsequent version of this License\n (see Section 10.2) or under the terms of a Secondary License (if permitted\n under the terms of Section 3.3).\n\n2.5. Representation\n\n Each Contributor represents that the Contributor believes its Contributions\n are its original creation(s) or it has sufficient rights to grant the\n rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n This License is not intended to limit any rights You have under applicable\n copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n All distribution of Covered Software in Source Code Form, including any\n Modifications that You create or to which You contribute, must be under the\n terms of this License. You must inform recipients that the Source Code Form\n of the Covered Software is governed by the terms of this License, and how\n they can obtain a copy of this License. You may not attempt to alter or\n restrict the recipients’ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n If You distribute Covered Software in Executable Form then:\n\n a. such Covered Software must also be made available in Source Code Form,\n as described in Section 3.1, and You must inform recipients of the\n Executable Form how they can obtain a copy of such Source Code Form by\n reasonable means in a timely manner, at a charge no more than the cost\n of distribution to the recipient; and\n\n b. You may distribute such Executable Form under the terms of this License,\n or sublicense it under different terms, provided that the license for\n the Executable Form does not attempt to limit or alter the recipients’\n rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n You may create and distribute a Larger Work under terms of Your choice,\n provided that You also comply with the requirements of this License for the\n Covered Software. If the Larger Work is a combination of Covered Software\n with a work governed by one or more Secondary Licenses, and the Covered\n Software is not Incompatible With Secondary Licenses, this License permits\n You to additionally distribute such Covered Software under the terms of\n such Secondary License(s), so that the recipient of the Larger Work may, at\n their option, further distribute the Covered Software under the terms of\n either this License or such Secondary License(s).\n\n3.4. Notices\n\n You may not remove or alter the substance of any license notices (including\n copyright notices, patent notices, disclaimers of warranty, or limitations\n of liability) contained within the Source Code Form of the Covered\n Software, except that You may alter any license notices to the extent\n required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n You may choose to offer, and to charge a fee for, warranty, support,\n indemnity or liability obligations to one or more recipients of Covered\n Software. However, You may do so only on Your own behalf, and not on behalf\n of any Contributor. You must make it absolutely clear that any such\n warranty, support, indemnity, or liability obligation is offered by You\n alone, and You hereby agree to indemnify every Contributor for any\n liability incurred by such Contributor as a result of warranty, support,\n indemnity or liability terms You offer. You may include additional\n disclaimers of warranty and limitations of liability specific to any\n jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n If it is impossible for You to comply with any of the terms of this License\n with respect to some or all of the Covered Software due to statute, judicial\n order, or regulation then You must: (a) comply with the terms of this License\n to the maximum extent possible; and (b) describe the limitations and the code\n they affect. Such description must be placed in a text file included with all\n distributions of the Covered Software under this License. Except to the\n extent prohibited by statute or regulation, such description must be\n sufficiently detailed for a recipient of ordinary skill to be able to\n understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n fail to comply with any of its terms. However, if You become compliant,\n then the rights granted under this License from a particular Contributor\n are reinstated (a) provisionally, unless and until such Contributor\n explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n if such Contributor fails to notify You of the non-compliance by some\n reasonable means prior to 60 days after You have come back into compliance.\n Moreover, Your grants from a particular Contributor are reinstated on an\n ongoing basis if such Contributor notifies You of the non-compliance by\n some reasonable means, this is the first time You have received notice of\n non-compliance with this License from such Contributor, and You become\n compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n infringement claim (excluding declaratory judgment actions, counter-claims,\n and cross-claims) alleging that a Contributor Version directly or\n indirectly infringes any patent, then the rights granted to You by any and\n all Contributors for the Covered Software under Section 2.1 of this License\n shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n license agreements (excluding distributors and resellers) which have been\n validly granted by You or Your distributors under this License prior to\n termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n Covered Software is provided under this License on an “as is” basis, without\n warranty of any kind, either expressed, implied, or statutory, including,\n without limitation, warranties that the Covered Software is free of defects,\n merchantable, fit for a particular purpose or non-infringing. The entire\n risk as to the quality and performance of the Covered Software is with You.\n Should any Covered Software prove defective in any respect, You (not any\n Contributor) assume the cost of any necessary servicing, repair, or\n correction. This disclaimer of warranty constitutes an essential part of this\n License. No use of any Covered Software is authorized under this License\n except under this disclaimer.\n\n7. Limitation of Liability\n\n Under no circumstances and under no legal theory, whether tort (including\n negligence), contract, or otherwise, shall any Contributor, or anyone who\n distributes Covered Software as permitted above, be liable to You for any\n direct, indirect, special, incidental, or consequential damages of any\n character including, without limitation, damages for lost profits, loss of\n goodwill, work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses, even if such party shall have been\n informed of the possibility of such damages. This limitation of liability\n shall not apply to liability for death or personal injury resulting from such\n party’s negligence to the extent applicable law prohibits such limitation.\n Some jurisdictions do not allow the exclusion or limitation of incidental or\n consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n Any litigation relating to this License may be brought only in the courts of\n a jurisdiction where the defendant maintains its principal place of business\n and such litigation shall be governed by laws of that jurisdiction, without\n reference to its conflict-of-law provisions. Nothing in this Section shall\n prevent a party’s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n This License represents the complete agreement concerning the subject matter\n hereof. If any provision of this License is held to be unenforceable, such\n provision shall be reformed only to the extent necessary to make it\n enforceable. Any law or regulation which provides that the language of a\n contract shall be construed against the drafter shall not be used to construe\n this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n Mozilla Foundation is the license steward. Except as provided in Section\n 10.3, no one other than the license steward has the right to modify or\n publish new versions of this License. Each version will be given a\n distinguishing version number.\n\n10.2. Effect of New Versions\n\n You may distribute the Covered Software under the terms of the version of\n the License under which You originally received the Covered Software, or\n under the terms of any subsequent version published by the license\n steward.\n\n10.3. Modified Versions\n\n If you create software not governed by this License, and you want to\n create a new license for such software, you may create and use a modified\n version of this License if you rename the license and remove any\n references to the name of the license steward (except to note that such\n modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n If You choose to distribute Source Code Form that is Incompatible With\n Secondary Licenses under the terms of this version of the License, the\n notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n This Source Code Form is subject to the\n terms of the Mozilla Public License, v.\n 2.0. If a copy of the MPL was not\n distributed with this file, You can\n obtain one at\n http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - “Incompatible With Secondary Licenses” Notice\n\n This Source Code Form is “Incompatible\n With Secondary Licenses”, as defined by\n the Mozilla Public License, v. 2.0.\n\n" - }, { "name": "github.com/hashicorp/go-cleanhttp", "path": "github.com/hashicorp/go-cleanhttp/LICENSE", "licenseText": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. \"Contributor\"\n\n means each individual or legal entity that creates, contributes to the\n creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n\n means the combination of the Contributions of others (if any) used by a\n Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n\n means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n\n means Source Code Form to which the initial Contributor has attached the\n notice in Exhibit A, the Executable Form of such Source Code Form, and\n Modifications of such Source Code Form, in each case including portions\n thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n means\n\n a. that the initial Contributor has attached the notice described in\n Exhibit B to the Covered Software; or\n\n b. that the Covered Software was made available under the terms of\n version 1.1 or earlier of the License, but not also under the terms of\n a Secondary License.\n\n1.6. \"Executable Form\"\n\n means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n\n means a work that combines Covered Software with other material, in a\n separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n\n means this document.\n\n1.9. \"Licensable\"\n\n means having the right to grant, to the maximum extent possible, whether\n at the time of the initial grant or subsequently, any and all of the\n rights conveyed by this License.\n\n1.10. \"Modifications\"\n\n means any of the following:\n\n a. any file in Source Code Form that results from an addition to,\n deletion from, or modification of the contents of Covered Software; or\n\n b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. \"Patent Claims\" of a Contributor\n\n means any patent claim(s), including without limitation, method,\n process, and apparatus claims, in any patent Licensable by such\n Contributor that would be infringed, but for the grant of the License,\n by the making, using, selling, offering for sale, having made, import,\n or transfer of either its Contributions or its Contributor Version.\n\n1.12. \"Secondary License\"\n\n means either the GNU General Public License, Version 2.0, the GNU Lesser\n General Public License, Version 2.1, the GNU Affero General Public\n License, Version 3.0, or any later versions of those licenses.\n\n1.13. \"Source Code Form\"\n\n means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n\n means an individual or a legal entity exercising rights under this\n License. For legal entities, \"You\" includes any entity that controls, is\n controlled by, or is under common control with You. For purposes of this\n definition, \"control\" means (a) the power, direct or indirect, to cause\n the direction or management of such entity, whether by contract or\n otherwise, or (b) ownership of more than fifty percent (50%) of the\n outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n Each Contributor hereby grants You a world-wide, royalty-free,\n non-exclusive license:\n\n a. under intellectual property rights (other than patent or trademark)\n Licensable by such Contributor to use, reproduce, make available,\n modify, display, perform, distribute, and otherwise exploit its\n Contributions, either on an unmodified basis, with Modifications, or\n as part of a Larger Work; and\n\n b. under Patent Claims of such Contributor to make, use, sell, offer for\n sale, have made, import, and otherwise transfer either its\n Contributions or its Contributor Version.\n\n2.2. Effective Date\n\n The licenses granted in Section 2.1 with respect to any Contribution\n become effective for each Contribution on the date the Contributor first\n distributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\n The licenses granted in this Section 2 are the only rights granted under\n this License. No additional rights or licenses will be implied from the\n distribution or licensing of Covered Software under this License.\n Notwithstanding Section 2.1(b) above, no patent license is granted by a\n Contributor:\n\n a. for any code that a Contributor has removed from Covered Software; or\n\n b. for infringements caused by: (i) Your and any other third party's\n modifications of Covered Software, or (ii) the combination of its\n Contributions with other software (except as part of its Contributor\n Version); or\n\n c. under Patent Claims infringed by Covered Software in the absence of\n its Contributions.\n\n This License does not grant any rights in the trademarks, service marks,\n or logos of any Contributor (except as may be necessary to comply with\n the notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n No Contributor makes additional grants as a result of Your choice to\n distribute the Covered Software under a subsequent version of this\n License (see Section 10.2) or under the terms of a Secondary License (if\n permitted under the terms of Section 3.3).\n\n2.5. Representation\n\n Each Contributor represents that the Contributor believes its\n Contributions are its original creation(s) or it has sufficient rights to\n grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n This License is not intended to limit any rights You have under\n applicable copyright doctrines of fair use, fair dealing, or other\n equivalents.\n\n2.7. Conditions\n\n Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n All distribution of Covered Software in Source Code Form, including any\n Modifications that You create or to which You contribute, must be under\n the terms of this License. You must inform recipients that the Source\n Code Form of the Covered Software is governed by the terms of this\n License, and how they can obtain a copy of this License. You may not\n attempt to alter or restrict the recipients' rights in the Source Code\n Form.\n\n3.2. Distribution of Executable Form\n\n If You distribute Covered Software in Executable Form then:\n\n a. such Covered Software must also be made available in Source Code Form,\n as described in Section 3.1, and You must inform recipients of the\n Executable Form how they can obtain a copy of such Source Code Form by\n reasonable means in a timely manner, at a charge no more than the cost\n of distribution to the recipient; and\n\n b. You may distribute such Executable Form under the terms of this\n License, or sublicense it under different terms, provided that the\n license for the Executable Form does not attempt to limit or alter the\n recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n You may create and distribute a Larger Work under terms of Your choice,\n provided that You also comply with the requirements of this License for\n the Covered Software. If the Larger Work is a combination of Covered\n Software with a work governed by one or more Secondary Licenses, and the\n Covered Software is not Incompatible With Secondary Licenses, this\n License permits You to additionally distribute such Covered Software\n under the terms of such Secondary License(s), so that the recipient of\n the Larger Work may, at their option, further distribute the Covered\n Software under the terms of either this License or such Secondary\n License(s).\n\n3.4. Notices\n\n You may not remove or alter the substance of any license notices\n (including copyright notices, patent notices, disclaimers of warranty, or\n limitations of liability) contained within the Source Code Form of the\n Covered Software, except that You may alter any license notices to the\n extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n You may choose to offer, and to charge a fee for, warranty, support,\n indemnity or liability obligations to one or more recipients of Covered\n Software. However, You may do so only on Your own behalf, and not on\n behalf of any Contributor. You must make it absolutely clear that any\n such warranty, support, indemnity, or liability obligation is offered by\n You alone, and You hereby agree to indemnify every Contributor for any\n liability incurred by such Contributor as a result of warranty, support,\n indemnity or liability terms You offer. You may include additional\n disclaimers of warranty and limitations of liability specific to any\n jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n If it is impossible for You to comply with any of the terms of this License\n with respect to some or all of the Covered Software due to statute,\n judicial order, or regulation then You must: (a) comply with the terms of\n this License to the maximum extent possible; and (b) describe the\n limitations and the code they affect. Such description must be placed in a\n text file included with all distributions of the Covered Software under\n this License. Except to the extent prohibited by statute or regulation,\n such description must be sufficiently detailed for a recipient of ordinary\n skill to be able to understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n fail to comply with any of its terms. However, if You become compliant,\n then the rights granted under this License from a particular Contributor\n are reinstated (a) provisionally, unless and until such Contributor\n explicitly and finally terminates Your grants, and (b) on an ongoing\n basis, if such Contributor fails to notify You of the non-compliance by\n some reasonable means prior to 60 days after You have come back into\n compliance. Moreover, Your grants from a particular Contributor are\n reinstated on an ongoing basis if such Contributor notifies You of the\n non-compliance by some reasonable means, this is the first time You have\n received notice of non-compliance with this License from such\n Contributor, and You become compliant prior to 30 days after Your receipt\n of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n infringement claim (excluding declaratory judgment actions,\n counter-claims, and cross-claims) alleging that a Contributor Version\n directly or indirectly infringes any patent, then the rights granted to\n You by any and all Contributors for the Covered Software under Section\n 2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n license agreements (excluding distributors and resellers) which have been\n validly granted by You or Your distributors under this License prior to\n termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n Covered Software is provided under this License on an \"as is\" basis,\n without warranty of any kind, either expressed, implied, or statutory,\n including, without limitation, warranties that the Covered Software is free\n of defects, merchantable, fit for a particular purpose or non-infringing.\n The entire risk as to the quality and performance of the Covered Software\n is with You. Should any Covered Software prove defective in any respect,\n You (not any Contributor) assume the cost of any necessary servicing,\n repair, or correction. This disclaimer of warranty constitutes an essential\n part of this License. No use of any Covered Software is authorized under\n this License except under this disclaimer.\n\n7. Limitation of Liability\n\n Under no circumstances and under no legal theory, whether tort (including\n negligence), contract, or otherwise, shall any Contributor, or anyone who\n distributes Covered Software as permitted above, be liable to You for any\n direct, indirect, special, incidental, or consequential damages of any\n character including, without limitation, damages for lost profits, loss of\n goodwill, work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses, even if such party shall have been\n informed of the possibility of such damages. This limitation of liability\n shall not apply to liability for death or personal injury resulting from\n such party's negligence to the extent applicable law prohibits such\n limitation. Some jurisdictions do not allow the exclusion or limitation of\n incidental or consequential damages, so this exclusion and limitation may\n not apply to You.\n\n8. Litigation\n\n Any litigation relating to this License may be brought only in the courts\n of a jurisdiction where the defendant maintains its principal place of\n business and such litigation shall be governed by laws of that\n jurisdiction, without reference to its conflict-of-law provisions. Nothing\n in this Section shall prevent a party's ability to bring cross-claims or\n counter-claims.\n\n9. Miscellaneous\n\n This License represents the complete agreement concerning the subject\n matter hereof. If any provision of this License is held to be\n unenforceable, such provision shall be reformed only to the extent\n necessary to make it enforceable. Any law or regulation which provides that\n the language of a contract shall be construed against the drafter shall not\n be used to construe this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n Mozilla Foundation is the license steward. Except as provided in Section\n 10.3, no one other than the license steward has the right to modify or\n publish new versions of this License. Each version will be given a\n distinguishing version number.\n\n10.2. Effect of New Versions\n\n You may distribute the Covered Software under the terms of the version\n of the License under which You originally received the Covered Software,\n or under the terms of any subsequent version published by the license\n steward.\n\n10.3. Modified Versions\n\n If you create software not governed by this License, and you want to\n create a new license for such software, you may create and use a\n modified version of this License if you rename the license and remove\n any references to the name of the license steward (except to note that\n such modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\n Licenses If You choose to distribute Source Code Form that is\n Incompatible With Secondary Licenses under the terms of this version of\n the License, the notice described in Exhibit B of this License must be\n attached.\n\nExhibit A - Source Code Form License Notice\n\n This Source Code Form is subject to the\n terms of the Mozilla Public License, v.\n 2.0. If a copy of the MPL was not\n distributed with this file, You can\n obtain one at\n http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file,\nthen You may include the notice in a location (such as a LICENSE file in a\nrelevant directory) where a recipient would be likely to look for such a\nnotice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n\n This Source Code Form is \"Incompatible\n With Secondary Licenses\", as defined by\n the Mozilla Public License, v. 2.0.\n\n" }, - { - "name": "github.com/hashicorp/go-multierror", - "path": "github.com/hashicorp/go-multierror/LICENSE", - "licenseText": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n means each individual or legal entity that creates, contributes to the\n creation of, or owns Covered Software.\n\n1.2. “Contributor Version”\n\n means the combination of the Contributions of others (if any) used by a\n Contributor and that particular Contributor’s Contribution.\n\n1.3. “Contribution”\n\n means Covered Software of a particular Contributor.\n\n1.4. “Covered Software”\n\n means Source Code Form to which the initial Contributor has attached the\n notice in Exhibit A, the Executable Form of such Source Code Form, and\n Modifications of such Source Code Form, in each case including portions\n thereof.\n\n1.5. “Incompatible With Secondary Licenses”\n means\n\n a. that the initial Contributor has attached the notice described in\n Exhibit B to the Covered Software; or\n\n b. that the Covered Software was made available under the terms of version\n 1.1 or earlier of the License, but not also under the terms of a\n Secondary License.\n\n1.6. “Executable Form”\n\n means any form of the work other than Source Code Form.\n\n1.7. “Larger Work”\n\n means a work that combines Covered Software with other material, in a separate\n file or files, that is not Covered Software.\n\n1.8. “License”\n\n means this document.\n\n1.9. “Licensable”\n\n means having the right to grant, to the maximum extent possible, whether at the\n time of the initial grant or subsequently, any and all of the rights conveyed by\n this License.\n\n1.10. “Modifications”\n\n means any of the following:\n\n a. any file in Source Code Form that results from an addition to, deletion\n from, or modification of the contents of Covered Software; or\n\n b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. “Patent Claims” of a Contributor\n\n means any patent claim(s), including without limitation, method, process,\n and apparatus claims, in any patent Licensable by such Contributor that\n would be infringed, but for the grant of the License, by the making,\n using, selling, offering for sale, having made, import, or transfer of\n either its Contributions or its Contributor Version.\n\n1.12. “Secondary License”\n\n means either the GNU General Public License, Version 2.0, the GNU Lesser\n General Public License, Version 2.1, the GNU Affero General Public\n License, Version 3.0, or any later versions of those licenses.\n\n1.13. “Source Code Form”\n\n means the form of the work preferred for making modifications.\n\n1.14. “You” (or “Your”)\n\n means an individual or a legal entity exercising rights under this\n License. For legal entities, “You” includes any entity that controls, is\n controlled by, or is under common control with You. For purposes of this\n definition, “control” means (a) the power, direct or indirect, to cause\n the direction or management of such entity, whether by contract or\n otherwise, or (b) ownership of more than fifty percent (50%) of the\n outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n Each Contributor hereby grants You a world-wide, royalty-free,\n non-exclusive license:\n\n a. under intellectual property rights (other than patent or trademark)\n Licensable by such Contributor to use, reproduce, make available,\n modify, display, perform, distribute, and otherwise exploit its\n Contributions, either on an unmodified basis, with Modifications, or as\n part of a Larger Work; and\n\n b. under Patent Claims of such Contributor to make, use, sell, offer for\n sale, have made, import, and otherwise transfer either its Contributions\n or its Contributor Version.\n\n2.2. Effective Date\n\n The licenses granted in Section 2.1 with respect to any Contribution become\n effective for each Contribution on the date the Contributor first distributes\n such Contribution.\n\n2.3. Limitations on Grant Scope\n\n The licenses granted in this Section 2 are the only rights granted under this\n License. No additional rights or licenses will be implied from the distribution\n or licensing of Covered Software under this License. Notwithstanding Section\n 2.1(b) above, no patent license is granted by a Contributor:\n\n a. for any code that a Contributor has removed from Covered Software; or\n\n b. for infringements caused by: (i) Your and any other third party’s\n modifications of Covered Software, or (ii) the combination of its\n Contributions with other software (except as part of its Contributor\n Version); or\n\n c. under Patent Claims infringed by Covered Software in the absence of its\n Contributions.\n\n This License does not grant any rights in the trademarks, service marks, or\n logos of any Contributor (except as may be necessary to comply with the\n notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n No Contributor makes additional grants as a result of Your choice to\n distribute the Covered Software under a subsequent version of this License\n (see Section 10.2) or under the terms of a Secondary License (if permitted\n under the terms of Section 3.3).\n\n2.5. Representation\n\n Each Contributor represents that the Contributor believes its Contributions\n are its original creation(s) or it has sufficient rights to grant the\n rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n This License is not intended to limit any rights You have under applicable\n copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n All distribution of Covered Software in Source Code Form, including any\n Modifications that You create or to which You contribute, must be under the\n terms of this License. You must inform recipients that the Source Code Form\n of the Covered Software is governed by the terms of this License, and how\n they can obtain a copy of this License. You may not attempt to alter or\n restrict the recipients’ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n If You distribute Covered Software in Executable Form then:\n\n a. such Covered Software must also be made available in Source Code Form,\n as described in Section 3.1, and You must inform recipients of the\n Executable Form how they can obtain a copy of such Source Code Form by\n reasonable means in a timely manner, at a charge no more than the cost\n of distribution to the recipient; and\n\n b. You may distribute such Executable Form under the terms of this License,\n or sublicense it under different terms, provided that the license for\n the Executable Form does not attempt to limit or alter the recipients’\n rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n You may create and distribute a Larger Work under terms of Your choice,\n provided that You also comply with the requirements of this License for the\n Covered Software. If the Larger Work is a combination of Covered Software\n with a work governed by one or more Secondary Licenses, and the Covered\n Software is not Incompatible With Secondary Licenses, this License permits\n You to additionally distribute such Covered Software under the terms of\n such Secondary License(s), so that the recipient of the Larger Work may, at\n their option, further distribute the Covered Software under the terms of\n either this License or such Secondary License(s).\n\n3.4. Notices\n\n You may not remove or alter the substance of any license notices (including\n copyright notices, patent notices, disclaimers of warranty, or limitations\n of liability) contained within the Source Code Form of the Covered\n Software, except that You may alter any license notices to the extent\n required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n You may choose to offer, and to charge a fee for, warranty, support,\n indemnity or liability obligations to one or more recipients of Covered\n Software. However, You may do so only on Your own behalf, and not on behalf\n of any Contributor. You must make it absolutely clear that any such\n warranty, support, indemnity, or liability obligation is offered by You\n alone, and You hereby agree to indemnify every Contributor for any\n liability incurred by such Contributor as a result of warranty, support,\n indemnity or liability terms You offer. You may include additional\n disclaimers of warranty and limitations of liability specific to any\n jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n If it is impossible for You to comply with any of the terms of this License\n with respect to some or all of the Covered Software due to statute, judicial\n order, or regulation then You must: (a) comply with the terms of this License\n to the maximum extent possible; and (b) describe the limitations and the code\n they affect. Such description must be placed in a text file included with all\n distributions of the Covered Software under this License. Except to the\n extent prohibited by statute or regulation, such description must be\n sufficiently detailed for a recipient of ordinary skill to be able to\n understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n fail to comply with any of its terms. However, if You become compliant,\n then the rights granted under this License from a particular Contributor\n are reinstated (a) provisionally, unless and until such Contributor\n explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n if such Contributor fails to notify You of the non-compliance by some\n reasonable means prior to 60 days after You have come back into compliance.\n Moreover, Your grants from a particular Contributor are reinstated on an\n ongoing basis if such Contributor notifies You of the non-compliance by\n some reasonable means, this is the first time You have received notice of\n non-compliance with this License from such Contributor, and You become\n compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n infringement claim (excluding declaratory judgment actions, counter-claims,\n and cross-claims) alleging that a Contributor Version directly or\n indirectly infringes any patent, then the rights granted to You by any and\n all Contributors for the Covered Software under Section 2.1 of this License\n shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n license agreements (excluding distributors and resellers) which have been\n validly granted by You or Your distributors under this License prior to\n termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n Covered Software is provided under this License on an “as is” basis, without\n warranty of any kind, either expressed, implied, or statutory, including,\n without limitation, warranties that the Covered Software is free of defects,\n merchantable, fit for a particular purpose or non-infringing. The entire\n risk as to the quality and performance of the Covered Software is with You.\n Should any Covered Software prove defective in any respect, You (not any\n Contributor) assume the cost of any necessary servicing, repair, or\n correction. This disclaimer of warranty constitutes an essential part of this\n License. No use of any Covered Software is authorized under this License\n except under this disclaimer.\n\n7. Limitation of Liability\n\n Under no circumstances and under no legal theory, whether tort (including\n negligence), contract, or otherwise, shall any Contributor, or anyone who\n distributes Covered Software as permitted above, be liable to You for any\n direct, indirect, special, incidental, or consequential damages of any\n character including, without limitation, damages for lost profits, loss of\n goodwill, work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses, even if such party shall have been\n informed of the possibility of such damages. This limitation of liability\n shall not apply to liability for death or personal injury resulting from such\n party’s negligence to the extent applicable law prohibits such limitation.\n Some jurisdictions do not allow the exclusion or limitation of incidental or\n consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n Any litigation relating to this License may be brought only in the courts of\n a jurisdiction where the defendant maintains its principal place of business\n and such litigation shall be governed by laws of that jurisdiction, without\n reference to its conflict-of-law provisions. Nothing in this Section shall\n prevent a party’s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n This License represents the complete agreement concerning the subject matter\n hereof. If any provision of this License is held to be unenforceable, such\n provision shall be reformed only to the extent necessary to make it\n enforceable. Any law or regulation which provides that the language of a\n contract shall be construed against the drafter shall not be used to construe\n this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n Mozilla Foundation is the license steward. Except as provided in Section\n 10.3, no one other than the license steward has the right to modify or\n publish new versions of this License. Each version will be given a\n distinguishing version number.\n\n10.2. Effect of New Versions\n\n You may distribute the Covered Software under the terms of the version of\n the License under which You originally received the Covered Software, or\n under the terms of any subsequent version published by the license\n steward.\n\n10.3. Modified Versions\n\n If you create software not governed by this License, and you want to\n create a new license for such software, you may create and use a modified\n version of this License if you rename the license and remove any\n references to the name of the license steward (except to note that such\n modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n If You choose to distribute Source Code Form that is Incompatible With\n Secondary Licenses under the terms of this version of the License, the\n notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n This Source Code Form is subject to the\n terms of the Mozilla Public License, v.\n 2.0. If a copy of the MPL was not\n distributed with this file, You can\n obtain one at\n http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - “Incompatible With Secondary Licenses” Notice\n\n This Source Code Form is “Incompatible\n With Secondary Licenses”, as defined by\n the Mozilla Public License, v. 2.0.\n" - }, { "name": "github.com/hashicorp/go-retryablehttp", "path": "github.com/hashicorp/go-retryablehttp/LICENSE", @@ -904,11 +899,6 @@ "path": "github.com/rivo/uniseg/LICENSE.txt", "licenseText": "MIT License\n\nCopyright (c) 2019 Oliver Kuederle\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, - { - "name": "github.com/robfig/cron", - "path": "github.com/robfig/cron/LICENSE", - "licenseText": "Copyright (C) 2012 Rob Figueiredo\nAll Rights Reserved.\n\nMIT LICENSE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" - }, { "name": "github.com/robfig/cron/v3", "path": "github.com/robfig/cron/v3/LICENSE", diff --git a/cmd/admin.go b/cmd/admin.go index a4f48b0513..49d0e4ef74 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -6,26 +6,13 @@ package cmd import ( "context" - "errors" "fmt" - "net/url" - "os" - "strings" - "text/tabwriter" - asymkey_model "code.gitea.io/gitea/models/asymkey" - auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/util" - auth_service "code.gitea.io/gitea/services/auth" - "code.gitea.io/gitea/services/auth/source/oauth2" - "code.gitea.io/gitea/services/auth/source/smtp" - repo_service "code.gitea.io/gitea/services/repository" "github.com/urfave/cli/v2" ) @@ -59,28 +46,16 @@ var ( }, } - microcmdRegenHooks = &cli.Command{ - Name: "hooks", - Usage: "Regenerate git-hooks", - Action: runRegenerateHooks, - } - - microcmdRegenKeys = &cli.Command{ - Name: "keys", - Usage: "Regenerate authorized_keys file", - Action: runRegenerateKeys, - } - subcmdAuth = &cli.Command{ Name: "auth", Usage: "Modify external auth providers", Subcommands: []*cli.Command{ microcmdAuthAddOauth, microcmdAuthUpdateOauth, - cmdAuthAddLdapBindDn, - cmdAuthUpdateLdapBindDn, - cmdAuthAddLdapSimpleAuth, - cmdAuthUpdateLdapSimpleAuth, + microcmdAuthAddLdapBindDn, + microcmdAuthUpdateLdapBindDn, + microcmdAuthAddLdapSimpleAuth, + microcmdAuthUpdateLdapSimpleAuth, microcmdAuthAddSMTP, microcmdAuthUpdateSMTP, microcmdAuthList, @@ -88,170 +63,6 @@ var ( }, } - microcmdAuthList = &cli.Command{ - Name: "list", - Usage: "List auth sources", - Action: runListAuth, - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "min-width", - Usage: "Minimal cell width including any padding for the formatted table", - Value: 0, - }, - &cli.IntFlag{ - Name: "tab-width", - Usage: "width of tab characters in formatted table (equivalent number of spaces)", - Value: 8, - }, - &cli.IntFlag{ - Name: "padding", - Usage: "padding added to a cell before computing its width", - Value: 1, - }, - &cli.StringFlag{ - Name: "pad-char", - Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`, - Value: "\t", - }, - &cli.BoolFlag{ - Name: "vertical-bars", - Usage: "Set to true to print vertical bars between columns", - }, - }, - } - - idFlag = &cli.Int64Flag{ - Name: "id", - Usage: "ID of authentication source", - } - - microcmdAuthDelete = &cli.Command{ - Name: "delete", - Usage: "Delete specific auth source", - Flags: []cli.Flag{idFlag}, - Action: runDeleteAuth, - } - - oauthCLIFlags = []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Value: "", - Usage: "Application Name", - }, - &cli.StringFlag{ - Name: "provider", - Value: "", - Usage: "OAuth2 Provider", - }, - &cli.StringFlag{ - Name: "key", - Value: "", - Usage: "Client ID (Key)", - }, - &cli.StringFlag{ - Name: "secret", - Value: "", - Usage: "Client Secret", - }, - &cli.StringFlag{ - Name: "auto-discover-url", - Value: "", - Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)", - }, - &cli.StringFlag{ - Name: "use-custom-urls", - Value: "false", - Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints", - }, - &cli.StringFlag{ - Name: "custom-tenant-id", - Value: "", - Usage: "Use custom Tenant ID for OAuth endpoints", - }, - &cli.StringFlag{ - Name: "custom-auth-url", - Value: "", - Usage: "Use a custom Authorization URL (option for GitLab/GitHub)", - }, - &cli.StringFlag{ - Name: "custom-token-url", - Value: "", - Usage: "Use a custom Token URL (option for GitLab/GitHub)", - }, - &cli.StringFlag{ - Name: "custom-profile-url", - Value: "", - Usage: "Use a custom Profile URL (option for GitLab/GitHub)", - }, - &cli.StringFlag{ - Name: "custom-email-url", - Value: "", - Usage: "Use a custom Email URL (option for GitHub)", - }, - &cli.StringFlag{ - Name: "icon-url", - Value: "", - Usage: "Custom icon URL for OAuth2 login source", - }, - &cli.BoolFlag{ - Name: "skip-local-2fa", - Usage: "Set to true to skip local 2fa for users authenticated by this source", - }, - &cli.StringSliceFlag{ - Name: "scopes", - Value: nil, - Usage: "Scopes to request when to authenticate against this OAuth2 source", - }, - &cli.StringFlag{ - Name: "required-claim-name", - Value: "", - Usage: "Claim name that has to be set to allow users to login with this source", - }, - &cli.StringFlag{ - Name: "required-claim-value", - Value: "", - Usage: "Claim value that has to be set to allow users to login with this source", - }, - &cli.StringFlag{ - Name: "group-claim-name", - Value: "", - Usage: "Claim name providing group names for this source", - }, - &cli.StringFlag{ - Name: "admin-group", - Value: "", - Usage: "Group Claim value for administrator users", - }, - &cli.StringFlag{ - Name: "restricted-group", - Value: "", - Usage: "Group Claim value for restricted users", - }, - &cli.StringFlag{ - Name: "group-team-map", - Value: "", - Usage: "JSON mapping between groups and org teams", - }, - &cli.BoolFlag{ - Name: "group-team-map-removal", - Usage: "Activate automatic team membership removal depending on groups", - }, - } - - microcmdAuthUpdateOauth = &cli.Command{ - Name: "update-oauth", - Usage: "Update existing Oauth authentication source", - Action: runUpdateOauth, - Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...), - } - - microcmdAuthAddOauth = &cli.Command{ - Name: "add-oauth", - Usage: "Add new Oauth authentication source", - Action: runAddOauth, - Flags: oauthCLIFlags, - } - subcmdSendMail = &cli.Command{ Name: "sendmail", Usage: "Send a message to all users", @@ -275,75 +86,9 @@ var ( }, } - smtpCLIFlags = []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Value: "", - Usage: "Application Name", - }, - &cli.StringFlag{ - Name: "auth-type", - Value: "PLAIN", - Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN", - }, - &cli.StringFlag{ - Name: "host", - Value: "", - Usage: "SMTP Host", - }, - &cli.IntFlag{ - Name: "port", - Usage: "SMTP Port", - }, - &cli.BoolFlag{ - Name: "force-smtps", - Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.", - Value: true, - }, - &cli.BoolFlag{ - Name: "skip-verify", - Usage: "Skip TLS verify.", - Value: true, - }, - &cli.StringFlag{ - Name: "helo-hostname", - Value: "", - Usage: "Hostname sent with HELO. Leave blank to send current hostname", - }, - &cli.BoolFlag{ - Name: "disable-helo", - Usage: "Disable SMTP helo.", - Value: true, - }, - &cli.StringFlag{ - Name: "allowed-domains", - Value: "", - Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')", - }, - &cli.BoolFlag{ - Name: "skip-local-2fa", - Usage: "Skip 2FA to log on.", - Value: true, - }, - &cli.BoolFlag{ - Name: "active", - Usage: "This Authentication Source is Activated.", - Value: true, - }, - } - - microcmdAuthAddSMTP = &cli.Command{ - Name: "add-smtp", - Usage: "Add new SMTP authentication source", - Action: runAddSMTP, - Flags: smtpCLIFlags, - } - - microcmdAuthUpdateSMTP = &cli.Command{ - Name: "update-smtp", - Usage: "Update existing SMTP authentication source", - Action: runUpdateSMTP, - Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...), + idFlag = &cli.Int64Flag{ + Name: "id", + Usage: "ID of authentication source", } ) @@ -389,7 +134,7 @@ func runRepoSyncReleases(_ *cli.Context) error { } log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum) - if err = repo_module.SyncReleasesWithTags(repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { log.Warn(" SyncReleasesWithTags: %v", err) gitRepo.Close() continue @@ -420,351 +165,3 @@ func getReleaseCount(ctx context.Context, id int64) (int64, error) { }, ) } - -func runRegenerateHooks(_ *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) -} - -func runRegenerateKeys(_ *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - return asymkey_model.RewriteAllPublicKeys() -} - -func parseOAuth2Config(c *cli.Context) *oauth2.Source { - var customURLMapping *oauth2.CustomURLMapping - if c.IsSet("use-custom-urls") { - customURLMapping = &oauth2.CustomURLMapping{ - TokenURL: c.String("custom-token-url"), - AuthURL: c.String("custom-auth-url"), - ProfileURL: c.String("custom-profile-url"), - EmailURL: c.String("custom-email-url"), - Tenant: c.String("custom-tenant-id"), - } - } else { - customURLMapping = nil - } - return &oauth2.Source{ - Provider: c.String("provider"), - ClientID: c.String("key"), - ClientSecret: c.String("secret"), - OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"), - CustomURLMapping: customURLMapping, - IconURL: c.String("icon-url"), - SkipLocalTwoFA: c.Bool("skip-local-2fa"), - Scopes: c.StringSlice("scopes"), - RequiredClaimName: c.String("required-claim-name"), - RequiredClaimValue: c.String("required-claim-value"), - GroupClaimName: c.String("group-claim-name"), - AdminGroup: c.String("admin-group"), - RestrictedGroup: c.String("restricted-group"), - GroupTeamMap: c.String("group-team-map"), - GroupTeamMapRemoval: c.Bool("group-team-map-removal"), - } -} - -func runAddOauth(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - - config := parseOAuth2Config(c) - if config.Provider == "openidConnect" { - discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL) - if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { - return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL) - } - } - - return auth_model.CreateSource(&auth_model.Source{ - Type: auth_model.OAuth2, - Name: c.String("name"), - IsActive: true, - Cfg: config, - }) -} - -func runUpdateOauth(c *cli.Context) error { - if !c.IsSet("id") { - return fmt.Errorf("--id flag is missing") - } - - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - - source, err := auth_model.GetSourceByID(c.Int64("id")) - if err != nil { - return err - } - - oAuth2Config := source.Cfg.(*oauth2.Source) - - if c.IsSet("name") { - source.Name = c.String("name") - } - - if c.IsSet("provider") { - oAuth2Config.Provider = c.String("provider") - } - - if c.IsSet("key") { - oAuth2Config.ClientID = c.String("key") - } - - if c.IsSet("secret") { - oAuth2Config.ClientSecret = c.String("secret") - } - - if c.IsSet("auto-discover-url") { - oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url") - } - - if c.IsSet("icon-url") { - oAuth2Config.IconURL = c.String("icon-url") - } - - if c.IsSet("scopes") { - oAuth2Config.Scopes = c.StringSlice("scopes") - } - - if c.IsSet("required-claim-name") { - oAuth2Config.RequiredClaimName = c.String("required-claim-name") - } - if c.IsSet("required-claim-value") { - oAuth2Config.RequiredClaimValue = c.String("required-claim-value") - } - - if c.IsSet("group-claim-name") { - oAuth2Config.GroupClaimName = c.String("group-claim-name") - } - if c.IsSet("admin-group") { - oAuth2Config.AdminGroup = c.String("admin-group") - } - if c.IsSet("restricted-group") { - oAuth2Config.RestrictedGroup = c.String("restricted-group") - } - if c.IsSet("group-team-map") { - oAuth2Config.GroupTeamMap = c.String("group-team-map") - } - if c.IsSet("group-team-map-removal") { - oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal") - } - - // update custom URL mapping - customURLMapping := &oauth2.CustomURLMapping{} - - if oAuth2Config.CustomURLMapping != nil { - customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL - customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL - customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL - customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL - customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant - } - if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") { - customURLMapping.TokenURL = c.String("custom-token-url") - } - - if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") { - customURLMapping.AuthURL = c.String("custom-auth-url") - } - - if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") { - customURLMapping.ProfileURL = c.String("custom-profile-url") - } - - if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") { - customURLMapping.EmailURL = c.String("custom-email-url") - } - - if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") { - customURLMapping.Tenant = c.String("custom-tenant-id") - } - - oAuth2Config.CustomURLMapping = customURLMapping - source.Cfg = oAuth2Config - - return auth_model.UpdateSource(source) -} - -func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { - if c.IsSet("auth-type") { - conf.Auth = c.String("auth-type") - validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} - if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) { - return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5") - } - conf.Auth = c.String("auth-type") - } - if c.IsSet("host") { - conf.Host = c.String("host") - } - if c.IsSet("port") { - conf.Port = c.Int("port") - } - if c.IsSet("allowed-domains") { - conf.AllowedDomains = c.String("allowed-domains") - } - if c.IsSet("force-smtps") { - conf.ForceSMTPS = c.Bool("force-smtps") - } - if c.IsSet("skip-verify") { - conf.SkipVerify = c.Bool("skip-verify") - } - if c.IsSet("helo-hostname") { - conf.HeloHostname = c.String("helo-hostname") - } - if c.IsSet("disable-helo") { - conf.DisableHelo = c.Bool("disable-helo") - } - if c.IsSet("skip-local-2fa") { - conf.SkipLocalTwoFA = c.Bool("skip-local-2fa") - } - return nil -} - -func runAddSMTP(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - - if !c.IsSet("name") || len(c.String("name")) == 0 { - return errors.New("name must be set") - } - if !c.IsSet("host") || len(c.String("host")) == 0 { - return errors.New("host must be set") - } - if !c.IsSet("port") { - return errors.New("port must be set") - } - active := true - if c.IsSet("active") { - active = c.Bool("active") - } - - var smtpConfig smtp.Source - if err := parseSMTPConfig(c, &smtpConfig); err != nil { - return err - } - - // If not set default to PLAIN - if len(smtpConfig.Auth) == 0 { - smtpConfig.Auth = "PLAIN" - } - - return auth_model.CreateSource(&auth_model.Source{ - Type: auth_model.SMTP, - Name: c.String("name"), - IsActive: active, - Cfg: &smtpConfig, - }) -} - -func runUpdateSMTP(c *cli.Context) error { - if !c.IsSet("id") { - return fmt.Errorf("--id flag is missing") - } - - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - - source, err := auth_model.GetSourceByID(c.Int64("id")) - if err != nil { - return err - } - - smtpConfig := source.Cfg.(*smtp.Source) - - if err := parseSMTPConfig(c, smtpConfig); err != nil { - return err - } - - if c.IsSet("name") { - source.Name = c.String("name") - } - - if c.IsSet("active") { - source.IsActive = c.Bool("active") - } - - source.Cfg = smtpConfig - - return auth_model.UpdateSource(source) -} - -func runListAuth(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - - authSources, err := auth_model.Sources() - if err != nil { - return err - } - - flags := tabwriter.AlignRight - if c.Bool("vertical-bars") { - flags |= tabwriter.Debug - } - - padChar := byte('\t') - if len(c.String("pad-char")) > 0 { - padChar = c.String("pad-char")[0] - } - - // loop through each source and print - w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags) - fmt.Fprintf(w, "ID\tName\tType\tEnabled\n") - for _, source := range authSources { - fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive) - } - w.Flush() - - return nil -} - -func runDeleteAuth(c *cli.Context) error { - if !c.IsSet("id") { - return fmt.Errorf("--id flag is missing") - } - - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - - source, err := auth_model.GetSourceByID(c.Int64("id")) - if err != nil { - return err - } - - return auth_service.DeleteSource(source) -} diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go new file mode 100644 index 0000000000..3b308d77f7 --- /dev/null +++ b/cmd/admin_auth.go @@ -0,0 +1,109 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "fmt" + "os" + "text/tabwriter" + + auth_model "code.gitea.io/gitea/models/auth" + auth_service "code.gitea.io/gitea/services/auth" + + "github.com/urfave/cli/v2" +) + +var ( + microcmdAuthDelete = &cli.Command{ + Name: "delete", + Usage: "Delete specific auth source", + Flags: []cli.Flag{idFlag}, + Action: runDeleteAuth, + } + microcmdAuthList = &cli.Command{ + Name: "list", + Usage: "List auth sources", + Action: runListAuth, + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "min-width", + Usage: "Minimal cell width including any padding for the formatted table", + Value: 0, + }, + &cli.IntFlag{ + Name: "tab-width", + Usage: "width of tab characters in formatted table (equivalent number of spaces)", + Value: 8, + }, + &cli.IntFlag{ + Name: "padding", + Usage: "padding added to a cell before computing its width", + Value: 1, + }, + &cli.StringFlag{ + Name: "pad-char", + Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`, + Value: "\t", + }, + &cli.BoolFlag{ + Name: "vertical-bars", + Usage: "Set to true to print vertical bars between columns", + }, + }, + } +) + +func runListAuth(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + + authSources, err := auth_model.Sources(ctx) + if err != nil { + return err + } + + flags := tabwriter.AlignRight + if c.Bool("vertical-bars") { + flags |= tabwriter.Debug + } + + padChar := byte('\t') + if len(c.String("pad-char")) > 0 { + padChar = c.String("pad-char")[0] + } + + // loop through each source and print + w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags) + fmt.Fprintf(w, "ID\tName\tType\tEnabled\n") + for _, source := range authSources { + fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive) + } + w.Flush() + + return nil +} + +func runDeleteAuth(c *cli.Context) error { + if !c.IsSet("id") { + return fmt.Errorf("--id flag is missing") + } + + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + + source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + if err != nil { + return err + } + + return auth_service.DeleteSource(ctx, source) +} diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index cfa1a23235..e3c81809f8 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -17,9 +17,9 @@ import ( type ( authService struct { initDB func(ctx context.Context) error - createAuthSource func(*auth.Source) error - updateAuthSource func(*auth.Source) error - getAuthSourceByID func(id int64) (*auth.Source, error) + createAuthSource func(context.Context, *auth.Source) error + updateAuthSource func(context.Context, *auth.Source) error + getAuthSourceByID func(ctx context.Context, id int64) (*auth.Source, error) } ) @@ -132,10 +132,10 @@ var ( ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, &cli.StringFlag{ Name: "user-dn", - Usage: "The user’s DN.", + Usage: "The user's DN.", }) - cmdAuthAddLdapBindDn = &cli.Command{ + microcmdAuthAddLdapBindDn = &cli.Command{ Name: "add-ldap", Usage: "Add new LDAP (via Bind DN) authentication source", Action: func(c *cli.Context) error { @@ -144,7 +144,7 @@ var ( Flags: ldapBindDnCLIFlags, } - cmdAuthUpdateLdapBindDn = &cli.Command{ + microcmdAuthUpdateLdapBindDn = &cli.Command{ Name: "update-ldap", Usage: "Update existing LDAP (via Bind DN) authentication source", Action: func(c *cli.Context) error { @@ -153,7 +153,7 @@ var ( Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), } - cmdAuthAddLdapSimpleAuth = &cli.Command{ + microcmdAuthAddLdapSimpleAuth = &cli.Command{ Name: "add-ldap-simple", Usage: "Add new LDAP (simple auth) authentication source", Action: func(c *cli.Context) error { @@ -162,7 +162,7 @@ var ( Flags: ldapSimpleAuthCLIFlags, } - cmdAuthUpdateLdapSimpleAuth = &cli.Command{ + microcmdAuthUpdateLdapSimpleAuth = &cli.Command{ Name: "update-ldap-simple", Usage: "Update existing LDAP (simple auth) authentication source", Action: func(c *cli.Context) error { @@ -289,12 +289,12 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { // getAuthSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. -func (a *authService) getAuthSource(c *cli.Context, authType auth.Type) (*auth.Source, error) { +func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) { if err := argsSet(c, "id"); err != nil { return nil, err } - authSource, err := a.getAuthSourceByID(c.Int64("id")) + authSource, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return nil, err } @@ -332,7 +332,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { return err } - return a.createAuthSource(authSource) + return a.createAuthSource(ctx, authSource) } // updateLdapBindDn updates a new LDAP via Bind DN authentication source. @@ -344,7 +344,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error { return err } - authSource, err := a.getAuthSource(c, auth.LDAP) + authSource, err := a.getAuthSource(ctx, c, auth.LDAP) if err != nil { return err } @@ -354,7 +354,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error { return err } - return a.updateAuthSource(authSource) + return a.updateAuthSource(ctx, authSource) } // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. @@ -383,7 +383,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { return err } - return a.createAuthSource(authSource) + return a.createAuthSource(ctx, authSource) } // updateLdapBindDn updates a new LDAP (simple auth) authentication source. @@ -395,7 +395,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { return err } - authSource, err := a.getAuthSource(c, auth.DLDAP) + authSource, err := a.getAuthSource(ctx, c, auth.DLDAP) if err != nil { return err } @@ -405,5 +405,5 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { return err } - return a.updateAuthSource(authSource) + return a.updateAuthSource(ctx, authSource) } diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index 210a6463c3..7791f3a9cc 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -210,15 +210,15 @@ func TestAddLdapBindDn(t *testing.T) { initDB: func(context.Context) error { return nil }, - createAuthSource: func(authSource *auth.Source) error { + createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createdAuthSource = authSource return nil }, - updateAuthSource: func(authSource *auth.Source) error { + updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { assert.FailNow(t, "case %d: should not call updateAuthSource", n) return nil }, - getAuthSourceByID: func(id int64) (*auth.Source, error) { + getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { assert.FailNow(t, "case %d: should not call getAuthSourceByID", n) return nil, nil }, @@ -226,7 +226,7 @@ func TestAddLdapBindDn(t *testing.T) { // Create a copy of command to test app := cli.NewApp() - app.Flags = cmdAuthAddLdapBindDn.Flags + app.Flags = microcmdAuthAddLdapBindDn.Flags app.Action = service.addLdapBindDn // Run it @@ -441,15 +441,15 @@ func TestAddLdapSimpleAuth(t *testing.T) { initDB: func(context.Context) error { return nil }, - createAuthSource: func(authSource *auth.Source) error { + createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createdAuthSource = authSource return nil }, - updateAuthSource: func(authSource *auth.Source) error { + updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { assert.FailNow(t, "case %d: should not call updateAuthSource", n) return nil }, - getAuthSourceByID: func(id int64) (*auth.Source, error) { + getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { assert.FailNow(t, "case %d: should not call getAuthSourceByID", n) return nil, nil }, @@ -457,7 +457,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { // Create a copy of command to test app := cli.NewApp() - app.Flags = cmdAuthAddLdapSimpleAuth.Flags + app.Flags = microcmdAuthAddLdapSimpleAuth.Flags app.Action = service.addLdapSimpleAuth // Run it @@ -896,15 +896,15 @@ func TestUpdateLdapBindDn(t *testing.T) { initDB: func(context.Context) error { return nil }, - createAuthSource: func(authSource *auth.Source) error { + createAuthSource: func(ctx context.Context, authSource *auth.Source) error { assert.FailNow(t, "case %d: should not call createAuthSource", n) return nil }, - updateAuthSource: func(authSource *auth.Source) error { + updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updatedAuthSource = authSource return nil }, - getAuthSourceByID: func(id int64) (*auth.Source, error) { + getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { if c.id != 0 { assert.Equal(t, c.id, id, "case %d: wrong id", n) } @@ -920,7 +920,7 @@ func TestUpdateLdapBindDn(t *testing.T) { // Create a copy of command to test app := cli.NewApp() - app.Flags = cmdAuthUpdateLdapBindDn.Flags + app.Flags = microcmdAuthUpdateLdapBindDn.Flags app.Action = service.updateLdapBindDn // Run it @@ -1286,15 +1286,15 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { initDB: func(context.Context) error { return nil }, - createAuthSource: func(authSource *auth.Source) error { + createAuthSource: func(ctx context.Context, authSource *auth.Source) error { assert.FailNow(t, "case %d: should not call createAuthSource", n) return nil }, - updateAuthSource: func(authSource *auth.Source) error { + updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updatedAuthSource = authSource return nil }, - getAuthSourceByID: func(id int64) (*auth.Source, error) { + getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { if c.id != 0 { assert.Equal(t, c.id, id, "case %d: wrong id", n) } @@ -1310,7 +1310,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { // Create a copy of command to test app := cli.NewApp() - app.Flags = cmdAuthUpdateLdapSimpleAuth.Flags + app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags app.Action = service.updateLdapSimpleAuth // Run it diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go new file mode 100644 index 0000000000..c151c0af27 --- /dev/null +++ b/cmd/admin_auth_oauth.go @@ -0,0 +1,298 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "fmt" + "net/url" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/services/auth/source/oauth2" + + "github.com/urfave/cli/v2" +) + +var ( + oauthCLIFlags = []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Value: "", + Usage: "Application Name", + }, + &cli.StringFlag{ + Name: "provider", + Value: "", + Usage: "OAuth2 Provider", + }, + &cli.StringFlag{ + Name: "key", + Value: "", + Usage: "Client ID (Key)", + }, + &cli.StringFlag{ + Name: "secret", + Value: "", + Usage: "Client Secret", + }, + &cli.StringFlag{ + Name: "auto-discover-url", + Value: "", + Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)", + }, + &cli.StringFlag{ + Name: "use-custom-urls", + Value: "false", + Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints", + }, + &cli.StringFlag{ + Name: "custom-tenant-id", + Value: "", + Usage: "Use custom Tenant ID for OAuth endpoints", + }, + &cli.StringFlag{ + Name: "custom-auth-url", + Value: "", + Usage: "Use a custom Authorization URL (option for GitLab/GitHub)", + }, + &cli.StringFlag{ + Name: "custom-token-url", + Value: "", + Usage: "Use a custom Token URL (option for GitLab/GitHub)", + }, + &cli.StringFlag{ + Name: "custom-profile-url", + Value: "", + Usage: "Use a custom Profile URL (option for GitLab/GitHub)", + }, + &cli.StringFlag{ + Name: "custom-email-url", + Value: "", + Usage: "Use a custom Email URL (option for GitHub)", + }, + &cli.StringFlag{ + Name: "icon-url", + Value: "", + Usage: "Custom icon URL for OAuth2 login source", + }, + &cli.BoolFlag{ + Name: "skip-local-2fa", + Usage: "Set to true to skip local 2fa for users authenticated by this source", + }, + &cli.StringSliceFlag{ + Name: "scopes", + Value: nil, + Usage: "Scopes to request when to authenticate against this OAuth2 source", + }, + &cli.StringFlag{ + Name: "required-claim-name", + Value: "", + Usage: "Claim name that has to be set to allow users to login with this source", + }, + &cli.StringFlag{ + Name: "required-claim-value", + Value: "", + Usage: "Claim value that has to be set to allow users to login with this source", + }, + &cli.StringFlag{ + Name: "group-claim-name", + Value: "", + Usage: "Claim name providing group names for this source", + }, + &cli.StringFlag{ + Name: "admin-group", + Value: "", + Usage: "Group Claim value for administrator users", + }, + &cli.StringFlag{ + Name: "restricted-group", + Value: "", + Usage: "Group Claim value for restricted users", + }, + &cli.StringFlag{ + Name: "group-team-map", + Value: "", + Usage: "JSON mapping between groups and org teams", + }, + &cli.BoolFlag{ + Name: "group-team-map-removal", + Usage: "Activate automatic team membership removal depending on groups", + }, + } + + microcmdAuthAddOauth = &cli.Command{ + Name: "add-oauth", + Usage: "Add new Oauth authentication source", + Action: runAddOauth, + Flags: oauthCLIFlags, + } + + microcmdAuthUpdateOauth = &cli.Command{ + Name: "update-oauth", + Usage: "Update existing Oauth authentication source", + Action: runUpdateOauth, + Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...), + } +) + +func parseOAuth2Config(c *cli.Context) *oauth2.Source { + var customURLMapping *oauth2.CustomURLMapping + if c.IsSet("use-custom-urls") { + customURLMapping = &oauth2.CustomURLMapping{ + TokenURL: c.String("custom-token-url"), + AuthURL: c.String("custom-auth-url"), + ProfileURL: c.String("custom-profile-url"), + EmailURL: c.String("custom-email-url"), + Tenant: c.String("custom-tenant-id"), + } + } else { + customURLMapping = nil + } + return &oauth2.Source{ + Provider: c.String("provider"), + ClientID: c.String("key"), + ClientSecret: c.String("secret"), + OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"), + CustomURLMapping: customURLMapping, + IconURL: c.String("icon-url"), + SkipLocalTwoFA: c.Bool("skip-local-2fa"), + Scopes: c.StringSlice("scopes"), + RequiredClaimName: c.String("required-claim-name"), + RequiredClaimValue: c.String("required-claim-value"), + GroupClaimName: c.String("group-claim-name"), + AdminGroup: c.String("admin-group"), + RestrictedGroup: c.String("restricted-group"), + GroupTeamMap: c.String("group-team-map"), + GroupTeamMapRemoval: c.Bool("group-team-map-removal"), + } +} + +func runAddOauth(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + + config := parseOAuth2Config(c) + if config.Provider == "openidConnect" { + discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL) + if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { + return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL) + } + } + + return auth_model.CreateSource(ctx, &auth_model.Source{ + Type: auth_model.OAuth2, + Name: c.String("name"), + IsActive: true, + Cfg: config, + }) +} + +func runUpdateOauth(c *cli.Context) error { + if !c.IsSet("id") { + return fmt.Errorf("--id flag is missing") + } + + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + + source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + if err != nil { + return err + } + + oAuth2Config := source.Cfg.(*oauth2.Source) + + if c.IsSet("name") { + source.Name = c.String("name") + } + + if c.IsSet("provider") { + oAuth2Config.Provider = c.String("provider") + } + + if c.IsSet("key") { + oAuth2Config.ClientID = c.String("key") + } + + if c.IsSet("secret") { + oAuth2Config.ClientSecret = c.String("secret") + } + + if c.IsSet("auto-discover-url") { + oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url") + } + + if c.IsSet("icon-url") { + oAuth2Config.IconURL = c.String("icon-url") + } + + if c.IsSet("scopes") { + oAuth2Config.Scopes = c.StringSlice("scopes") + } + + if c.IsSet("required-claim-name") { + oAuth2Config.RequiredClaimName = c.String("required-claim-name") + } + if c.IsSet("required-claim-value") { + oAuth2Config.RequiredClaimValue = c.String("required-claim-value") + } + + if c.IsSet("group-claim-name") { + oAuth2Config.GroupClaimName = c.String("group-claim-name") + } + if c.IsSet("admin-group") { + oAuth2Config.AdminGroup = c.String("admin-group") + } + if c.IsSet("restricted-group") { + oAuth2Config.RestrictedGroup = c.String("restricted-group") + } + if c.IsSet("group-team-map") { + oAuth2Config.GroupTeamMap = c.String("group-team-map") + } + if c.IsSet("group-team-map-removal") { + oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal") + } + + // update custom URL mapping + customURLMapping := &oauth2.CustomURLMapping{} + + if oAuth2Config.CustomURLMapping != nil { + customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL + customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL + customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL + customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL + customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant + } + if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") { + customURLMapping.TokenURL = c.String("custom-token-url") + } + + if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") { + customURLMapping.AuthURL = c.String("custom-auth-url") + } + + if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") { + customURLMapping.ProfileURL = c.String("custom-profile-url") + } + + if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") { + customURLMapping.EmailURL = c.String("custom-email-url") + } + + if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") { + customURLMapping.Tenant = c.String("custom-tenant-id") + } + + oAuth2Config.CustomURLMapping = customURLMapping + source.Cfg = oAuth2Config + + return auth_model.UpdateSource(ctx, source) +} diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go new file mode 100644 index 0000000000..58a6e2ac22 --- /dev/null +++ b/cmd/admin_auth_stmp.go @@ -0,0 +1,201 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "errors" + "fmt" + "strings" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/auth/source/smtp" + + "github.com/urfave/cli/v2" +) + +var ( + smtpCLIFlags = []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Value: "", + Usage: "Application Name", + }, + &cli.StringFlag{ + Name: "auth-type", + Value: "PLAIN", + Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN", + }, + &cli.StringFlag{ + Name: "host", + Value: "", + Usage: "SMTP Host", + }, + &cli.IntFlag{ + Name: "port", + Usage: "SMTP Port", + }, + &cli.BoolFlag{ + Name: "force-smtps", + Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.", + Value: true, + }, + &cli.BoolFlag{ + Name: "skip-verify", + Usage: "Skip TLS verify.", + Value: true, + }, + &cli.StringFlag{ + Name: "helo-hostname", + Value: "", + Usage: "Hostname sent with HELO. Leave blank to send current hostname", + }, + &cli.BoolFlag{ + Name: "disable-helo", + Usage: "Disable SMTP helo.", + Value: true, + }, + &cli.StringFlag{ + Name: "allowed-domains", + Value: "", + Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')", + }, + &cli.BoolFlag{ + Name: "skip-local-2fa", + Usage: "Skip 2FA to log on.", + Value: true, + }, + &cli.BoolFlag{ + Name: "active", + Usage: "This Authentication Source is Activated.", + Value: true, + }, + } + + microcmdAuthAddSMTP = &cli.Command{ + Name: "add-smtp", + Usage: "Add new SMTP authentication source", + Action: runAddSMTP, + Flags: smtpCLIFlags, + } + + microcmdAuthUpdateSMTP = &cli.Command{ + Name: "update-smtp", + Usage: "Update existing SMTP authentication source", + Action: runUpdateSMTP, + Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...), + } +) + +func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { + if c.IsSet("auth-type") { + conf.Auth = c.String("auth-type") + validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} + if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) { + return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5") + } + conf.Auth = c.String("auth-type") + } + if c.IsSet("host") { + conf.Host = c.String("host") + } + if c.IsSet("port") { + conf.Port = c.Int("port") + } + if c.IsSet("allowed-domains") { + conf.AllowedDomains = c.String("allowed-domains") + } + if c.IsSet("force-smtps") { + conf.ForceSMTPS = c.Bool("force-smtps") + } + if c.IsSet("skip-verify") { + conf.SkipVerify = c.Bool("skip-verify") + } + if c.IsSet("helo-hostname") { + conf.HeloHostname = c.String("helo-hostname") + } + if c.IsSet("disable-helo") { + conf.DisableHelo = c.Bool("disable-helo") + } + if c.IsSet("skip-local-2fa") { + conf.SkipLocalTwoFA = c.Bool("skip-local-2fa") + } + return nil +} + +func runAddSMTP(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + + if !c.IsSet("name") || len(c.String("name")) == 0 { + return errors.New("name must be set") + } + if !c.IsSet("host") || len(c.String("host")) == 0 { + return errors.New("host must be set") + } + if !c.IsSet("port") { + return errors.New("port must be set") + } + active := true + if c.IsSet("active") { + active = c.Bool("active") + } + + var smtpConfig smtp.Source + if err := parseSMTPConfig(c, &smtpConfig); err != nil { + return err + } + + // If not set default to PLAIN + if len(smtpConfig.Auth) == 0 { + smtpConfig.Auth = "PLAIN" + } + + return auth_model.CreateSource(ctx, &auth_model.Source{ + Type: auth_model.SMTP, + Name: c.String("name"), + IsActive: active, + Cfg: &smtpConfig, + }) +} + +func runUpdateSMTP(c *cli.Context) error { + if !c.IsSet("id") { + return fmt.Errorf("--id flag is missing") + } + + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + + source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + if err != nil { + return err + } + + smtpConfig := source.Cfg.(*smtp.Source) + + if err := parseSMTPConfig(c, smtpConfig); err != nil { + return err + } + + if c.IsSet("name") { + source.Name = c.String("name") + } + + if c.IsSet("active") { + source.IsActive = c.Bool("active") + } + + source.Cfg = smtpConfig + + return auth_model.UpdateSource(ctx, source) +} diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go new file mode 100644 index 0000000000..0db505ff9c --- /dev/null +++ b/cmd/admin_regenerate.go @@ -0,0 +1,46 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/modules/graceful" + repo_service "code.gitea.io/gitea/services/repository" + + "github.com/urfave/cli/v2" +) + +var ( + microcmdRegenHooks = &cli.Command{ + Name: "hooks", + Usage: "Regenerate git-hooks", + Action: runRegenerateHooks, + } + + microcmdRegenKeys = &cli.Command{ + Name: "keys", + Usage: "Regenerate authorized_keys file", + Action: runRegenerateKeys, + } +) + +func runRegenerateHooks(_ *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) +} + +func runRegenerateKeys(_ *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + return asymkey_model.RewriteAllPublicKeys(ctx) +} diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 52ce46c353..fefe18d39c 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -115,7 +115,7 @@ func runCreateUser(c *cli.Context) error { // If this is the first user being created. // Take it as the admin and don't force a password update. - if n := user_model.CountUsers(nil); n == 0 { + if n := user_model.CountUsers(ctx, nil); n == 0 { changePassword = false } @@ -146,7 +146,7 @@ func runCreateUser(c *cli.Context) error { IsRestricted: restricted, } - if err := user_model.CreateUser(u, overwriteDefault); err != nil { + if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil { return fmt.Errorf("CreateUser: %w", err) } @@ -156,7 +156,7 @@ func runCreateUser(c *cli.Context) error { UID: u.ID, } - if err := auth_model.NewAccessToken(t); err != nil { + if err := auth_model.NewAccessToken(ctx, t); err != nil { return err } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 0febb91661..6e78939680 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -63,7 +63,7 @@ func runGenerateAccessToken(c *cli.Context) error { UID: user.ID, } - exist, err := auth_model.AccessTokenByNameExists(t) + exist, err := auth_model.AccessTokenByNameExists(ctx, t) if err != nil { return err } @@ -79,7 +79,7 @@ func runGenerateAccessToken(c *cli.Context) error { t.Scope = accessTokenScope // create the token - if err := auth_model.NewAccessToken(t); err != nil { + if err := auth_model.NewAccessToken(ctx, t); err != nil { return err } diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index 9db9b5e56f..4c2b26d1df 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -33,7 +33,7 @@ func runListUsers(c *cli.Context) error { return err } - users, err := user_model.GetAllUsers() + users, err := user_model.GetAllUsers(ctx) if err != nil { return err } @@ -48,7 +48,7 @@ func runListUsers(c *cli.Context) error { } } } else { - twofa := user_model.UserList(users).GetTwoFaStatus() + twofa := user_model.UserList(users).GetTwoFaStatus(ctx) fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n") for _, u := range users { fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID]) diff --git a/cmd/main_test.go b/cmd/main_test.go index 0e32dce564..a916c61f85 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -20,9 +20,7 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: "..", - }) + unittest.MainTest(m) } func makePathOutput(workPath, customPath, customConf string) string { diff --git a/cmd/migrate_storage_test.go b/cmd/migrate_storage_test.go index 644e0dc18b..5d8c867993 100644 --- a/cmd/migrate_storage_test.go +++ b/cmd/migrate_storage_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -30,7 +31,7 @@ func TestMigratePackages(t *testing.T) { assert.NoError(t, err) defer buf.Close() - v, f, err := packages_service.CreatePackageAndAddFile(&packages_service.PackageCreationInfo{ + v, f, err := packages_service.CreatePackageAndAddFile(db.DefaultContext, &packages_service.PackageCreationInfo{ PackageInfo: packages_service.PackageInfo{ Owner: creator, PackageType: packages.TypeGeneric, diff --git a/contrib/fixtures/fixture_generation.go b/contrib/fixtures/fixture_generation.go index 06c0354efa..31797cc800 100644 --- a/contrib/fixtures/fixture_generation.go +++ b/contrib/fixtures/fixture_generation.go @@ -5,6 +5,7 @@ package main import ( + "context" "fmt" "os" "path/filepath" @@ -18,7 +19,7 @@ import ( var ( generators = []struct { - gen func() (string, error) + gen func(ctx context.Context) (string, error) name string }{ { @@ -41,16 +42,17 @@ func main() { fmt.Printf("PrepareTestDatabase: %+v\n", err) os.Exit(1) } + ctx := context.Background() if len(os.Args) == 0 { for _, r := range os.Args { - if err := generate(r); err != nil { + if err := generate(ctx, r); err != nil { fmt.Printf("generate '%s': %+v\n", r, err) os.Exit(1) } } } else { for _, g := range generators { - if err := generate(g.name); err != nil { + if err := generate(ctx, g.name); err != nil { fmt.Printf("generate '%s': %+v\n", g.name, err) os.Exit(1) } @@ -58,10 +60,10 @@ func main() { } } -func generate(name string) error { +func generate(ctx context.Context, name string) error { for _, g := range generators { if g.name == name { - data, err := g.gen() + data, err := g.gen(ctx) if err != nil { return err } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f0b0cc298b..2209822ff0 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -60,6 +60,7 @@ RUN_USER = ; git ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; The protocol the server listens on. One of 'http', 'https', 'http+unix', 'fcgi' or 'fcgi+unix'. Defaults to 'http' +;; Note: Value must be lowercase. ;PROTOCOL = http ;; ;; Expect PROXY protocol headers on connections @@ -548,7 +549,8 @@ ENABLE = true ;; Pre-register OAuth2 applications for some universally useful services ;; * https://github.com/hickford/git-credential-oauth ;; * https://github.com/git-ecosystem/git-credential-manager -;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager +;; * https://gitea.com/gitea/tea +;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, tea ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -950,7 +952,7 @@ LEVEL = Info ;; Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility. ;; External wiki and issue tracker can't be enabled by default as it requires additional settings. ;; Disabled repo units will not be added to new repositories regardless if it is in the default list. -;DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages +;DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages,repo.actions ;; ;; Comma separated list of default forked repo units. ;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS. @@ -1209,10 +1211,10 @@ LEVEL = Info ;SHOW_USER_EMAIL = true ;; ;; Set the default theme for the Gitea install -;DEFAULT_THEME = auto +;DEFAULT_THEME = gitea-auto ;; ;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. -;THEMES = auto,gitea,arc-green +;THEMES = gitea-auto,gitea-light,gitea-dark ;; ;; All available reactions users can choose on issues/prs and comments. ;; Values can be emoji alias (:smile:) or a unicode emoji. @@ -1420,7 +1422,7 @@ LEVEL = Info ;DATADIR = queues/ ; Relative paths will be made absolute against `%(APP_DATA_PATH)s`. ;; ;; Default queue length before a channel queue will block -;LENGTH = 100 +;LENGTH = 100000 ;; ;; Batch size to send for batched queues ;BATCH_LENGTH = 20 @@ -2562,12 +2564,18 @@ LEVEL = Info ; [actions] ;; Enable/Disable actions capabilities -;ENABLED = false +;ENABLED = true ;; ;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance. ;DEFAULT_ACTIONS_URL = github ;; Default artifact retention time in days, default is 90 days ;ARTIFACT_RETENTION_DAYS = 90 +;; Timeout to stop the task which have running status, but haven't been updated for a long time +;ZOMBIE_TASK_TIMEOUT = 10m +;; Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time +;ENDLESS_TASK_TIMEOUT = 3h +;; Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time +;ABANDONED_JOB_TIMEOUT = 24h ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration.fr-fr.md b/docs/content/administration.fr-fr.md deleted file mode 100644 index ed11881b77..0000000000 --- a/docs/content/administration.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Avancé" -slug: "administration" -sidebar_position: 30 -toc: false -draft: false -menu: - sidebar: - name: "Avancé" - sidebar_position: 20 - identifier: "administration" ---- diff --git a/docs/content/administration.zh-tw.md b/docs/content/administration.zh-tw.md deleted file mode 100644 index 455d6a363f..0000000000 --- a/docs/content/administration.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "運維" -slug: "administration" -sidebar_position: 30 -toc: false -draft: false -menu: - sidebar: - name: "運維" - sidebar_position: 20 - identifier: "administration" ---- diff --git a/docs/content/administration/_index.zh-tw.md b/docs/content/administration/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/administration/backup-and-restore.zh-tw.md b/docs/content/administration/backup-and-restore.zh-tw.md deleted file mode 100644 index 4966ccdc50..0000000000 --- a/docs/content/administration/backup-and-restore.zh-tw.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -date: "2017-01-01T16:00:00+02:00" -title: "用法: 備份與還原" -slug: "backup-and-restore" -sidebar_position: 11 -toc: false -draft: false -aliases: - - /zh-tw/backup-and-restore -menu: - sidebar: - parent: "administration" - name: "備份與還原" - sidebar_position: 11 - identifier: "backup-and-restore" ---- - -# 備份與還原 - -Gitea 目前支援 `dump` 指令,用來將資料備份成 zip 檔案,後續會提供還原指令,讓你可以輕易的將備份資料及還原到另外一台機器。 - -## 備份指令 (`dump`) - -首先,切換到執行 Gitea 的使用者: `su git` (請修改成您指定的使用者),並在安裝目錄內執行 `./gitea dump` 指令,你可以看到 console 畫面如下: - -``` -2016/12/27 22:32:09 Creating tmp work dir: /tmp/gitea-dump-417443001 -2016/12/27 22:32:09 Dumping local repositories.../home/git/gitea-repositories -2016/12/27 22:32:22 Dumping database... -2016/12/27 22:32:22 Packing dump files... -2016/12/27 22:32:34 Removing tmp work dir: /tmp/gitea-dump-417443001 -2016/12/27 22:32:34 Finish dumping in file gitea-dump-1482906742.zip -``` - -備份出來的 `gitea-dump-1482906742.zip` 檔案,檔案內會包含底下內容: - -* `custom/conf/app.ini` - 伺服器設定檔。 -* `gitea-db.sql` - SQL 備份檔案。 -* `gitea-repo.zip` - 此 zip 檔案為全部的 repo 目錄。 - 請參考 Config -> repository -> `ROOT` 所設定的路徑。 -* `log/` - 全部 logs 檔案,如果你要 migrate 到其他伺服器,此目錄不用保留。 - -你可以透過設定 `--tempdir` 指令參數來指定備份檔案目錄,或者是設定 `TMPDIR` 環境變數來達到此功能。 - -## 還原指令 (`restore`) - -持續更新中: 此文件尚未完成. - -例: - -```sh -unzip gitea-dump-1610949662.zip -cd gitea-dump-1610949662 -mv data/conf/app.ini /etc/gitea/conf/app.ini -mv data/* /var/lib/gitea/data/ -mv log/* /var/lib/gitea/log/ -mv repos/* /var/lib/gitea/repositories/ -chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea - -# mysql -mysql --default-character-set=utf8mb4 -u$USER -p$PASS $DATABASE **: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore. - `SECRET_KEY_URI`: **_empty_**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY. - `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days. -- `COOKIE_USERNAME`: **gitea\_awesome**: Name of the cookie used to store the current username. - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication information. - `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: Header name for reverse proxy @@ -617,7 +617,7 @@ And the following unique queues: - `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page or to use API. - `ENABLE_NOTIFY_MAIL`: **false**: Enable this to send e-mail to watchers of a repository when something happens, like creating issues. Requires `Mailer` to be enabled. -- `ENABLE_BASIC_AUTHENTICATION`: **true**: Disable this to disallow authenticaton using HTTP +- `ENABLE_BASIC_AUTHENTICATION`: **true**: Disable this to disallow authentication using HTTP BASIC and the user's password. Please note if you disable this you will not be able to access the tokens API endpoints using a password. Further, this only disables BASIC authentication using the password - not tokens or OAuth Basic. @@ -1107,7 +1107,7 @@ This section only does "set" config, a removed config key from this section won' - `JWT_SECRET_URI`: **_empty_**: Instead of defining JWT_SECRET in the configuration, this configuration option can be used to give Gitea a path to a file that contains the secret (example value: `file:/etc/gitea/oauth2_jwt_secret`) - `JWT_SIGNING_PRIVATE_KEY_FILE`: **jwt/private.pem**: Private key file path used to sign OAuth2 tokens. The path is relative to `APP_DATA_PATH`. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `RS256`, `RS384`, `RS512`, `ES256`, `ES384` or `ES512`. The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you. - `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider -- `DEFAULT_APPLICATIONS`: **git-credential-oauth, git-credential-manager**: Pre-register OAuth applications for some services on startup. See the [OAuth2 documentation](/development/oauth2-provider.md) for the list of available options. +- `DEFAULT_APPLICATIONS`: **git-credential-oauth, git-credential-manager, tea**: Pre-register OAuth applications for some services on startup. See the [OAuth2 documentation](/development/oauth2-provider.md) for the list of available options. ## i18n (`i18n`) @@ -1277,7 +1277,7 @@ Default storage configuration for attachments, lfs, avatars, repo-avatars, repo- - `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` - `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio` -The recommanded storage configuration for minio like below: +The recommended storage configuration for minio like below: ```ini [storage] @@ -1384,11 +1384,14 @@ PROXY_HOSTS = *.github.com ## Actions (`actions`) -- `ENABLED`: **false**: Enable/Disable actions capabilities +- `ENABLED`: **true**: Enable/Disable actions capabilities - `DEFAULT_ACTIONS_URL`: **github**: Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance. - `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` - `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio` - `ARTIFACT_RETENTION_DAYS`: **90**: Number of days to keep artifacts. Set to 0 to disable artifact retention. Default is 90 days if not set. +- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time +- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time +- `ABANDONED_JOB_TIMEOUT`: **24h**: Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time `DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path. For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`. @@ -1398,7 +1401,7 @@ Please note that using `self` is not recommended for most cases, as it could mak Additionally, it requires you to mirror all the actions you need to your Gitea instance, which may not be worth it. Therefore, please use `self` only if you understand what you are doing. -In earlier versions (<= 1.19), `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`. +In earlier versions (<= 1.19), `DEFAULT_ACTIONS_URL` could be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`. However, later updates removed those options, and now the only options are `github` and `self`, with the default value being `github`. However, if you want to use actions from other git server, you can use a complete URL in `uses` field, it's supported by Gitea (but not GitHub). Like `uses: https://gitea.com/actions/checkout@v3` or `uses: http://your-git-server/actions/checkout@v3`. diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md index b3e7d1024a..d541013159 100644 --- a/docs/content/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/administration/config-cheat-sheet.zh-cn.md @@ -102,7 +102,7 @@ menu: - `ENABLE_PUSH_CREATE_USER`: **false**: 允许用户将本地存储库推送到Gitea,并为用户自动创建它们。 - `ENABLE_PUSH_CREATE_ORG`: **false**: 允许用户将本地存储库推送到Gitea,并为组织自动创建它们。 - `DISABLED_REPO_UNITS`: **_empty_**: 逗号分隔的全局禁用的仓库单元列表。允许的值是:: \[repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions\] -- `DEFAULT_REPO_UNITS`: **repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages**: 逗号分隔的默认新仓库单元列表。允许的值是:: \[repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects, repo.packages, repo.actions\]. 注意:目前无法停用代码和发布。如果您指定了默认的仓库单元,您仍应将它们列出以保持未来的兼容性。外部wiki和问题跟踪器不能默认启用,因为它需要额外的设置。禁用的仓库单元将不会添加到新的仓库中,无论它是否在默认列表中。 +- `DEFAULT_REPO_UNITS`: **repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages,repo.actions**: 逗号分隔的默认新仓库单元列表。允许的值是:: \[repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects, repo.packages, repo.actions\]. 注意:目前无法停用代码和发布。如果您指定了默认的仓库单元,您仍应将它们列出以保持未来的兼容性。外部wiki和问题跟踪器不能默认启用,因为它需要额外的设置。禁用的仓库单元将不会添加到新的仓库中,无论它是否在默认列表中。 - `DEFAULT_FORK_REPO_UNITS`: **repo.code,repo.pulls**: 逗号分隔的默认分叉仓库单元列表。允许的值和规则与`DEFAULT_REPO_UNITS`相同。 - `PREFIX_ARCHIVE_FILES`: **true**: 通过将存档文件放置在以仓库命名的目录中来添加前缀。 - `DISABLE_MIGRATIONS`: **false**: 禁用迁移功能。 @@ -214,9 +214,9 @@ menu: - `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。 - `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。 - `CODE_COMMENT_LINES`: **4**: 在代码评论中能够显示的最大代码行数。 -- `DEFAULT_THEME`: **auto**: \[auto, gitea, arc-green\]: 在Gitea安装时候设置的默认主题。 +- `DEFAULT_THEME`: **gitea-auto**: \[gitea-auto, gitea-light, gitea-dark\]: 在Gitea安装时候设置的默认主题。 - `SHOW_USER_EMAIL`: **true**: 用户的电子邮件是否应该显示在`Explore Users`页面中。 -- `THEMES`: **auto,gitea,arc-green**: 所有可用的主题。允许用户选择个性化的主题, +- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: 所有可用的主题。允许用户选择个性化的主题, 而不受DEFAULT_THEME 值的影响。 - `MAX_DISPLAY_FILE_SIZE`: **8388608**: 能够显示文件的最大大小(默认为8MiB)。 - `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。 @@ -472,7 +472,7 @@ menu: - `TYPE`:**level**:通用队列类型,当前支持:`level`(在内部使用 LevelDB)、`channel`、`redis`、`dummy`。无效的类型将视为 `level`。 - `DATADIR`:**queues/common**:用于存储 level 队列的基本 DataDir。单独的队列的 `DATADIR` 可以在 `queue.name` 部分进行设置。相对路径将根据 `%(APP_DATA_PATH)s` 变为绝对路径。 -- `LENGTH`:**100**:通道队列阻塞之前的最大队列大小 +- `LENGTH`:**100000**:通道队列阻塞之前的最大队列大小 - `BATCH_LENGTH`:**20**:在传递给处理程序之前批处理数据 - `CONN_STR`:**redis://127.0.0.1:6379/0**:redis 队列类型的连接字符串。对于 `redis-cluster`,使用 `redis+cluster://127.0.0.1:6379/0`。可以使用查询参数来设置选项。类似地,LevelDB 选项也可以使用:**leveldb://relative/path?option=value** 或 **leveldb:///absolute/path?option=value** 进行设置,并将覆盖 `DATADIR`。 - `QUEUE_NAME`:**_queue**:默认的 redis 和磁盘队列名称的后缀。单独的队列将默认为 **`name`**`QUEUE_NAME`,但可以在特定的 `queue.name` 部分中进行覆盖。 @@ -506,7 +506,6 @@ Gitea 创建以下非唯一队列: - `SECRET_KEY`: **\<每次安装时随机生成\>**:全局服务器安全密钥。这个密钥非常重要,如果丢失将无法解密加密的数据(例如 2FA)。 - `SECRET_KEY_URI`: **_empty_**:与定义 `SECRET_KEY` 不同,此选项可用于使用存储在文件中的密钥(示例值:`file:/etc/gitea/secret_key`)。它不应该像 `SECRET_KEY` 一样容易丢失。 - `LOGIN_REMEMBER_DAYS`: **7**:Cookie 保存时间,单位为天。 -- `COOKIE_USERNAME`: **gitea\_awesome**:保存用户名的 Cookie 名称。 - `COOKIE_REMEMBER_NAME`: **gitea\_incredible**:保存自动登录信息的 Cookie 名称。 - `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**:反向代理认证的 HTTP 头部名称,用于提供用户信息。 - `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**:反向代理认证的 HTTP 头部名称,用于提供邮箱信息。 @@ -1056,7 +1055,7 @@ Gitea 创建以下非唯一队列: - `JWT_SECRET_URI`:**_empty_**:可以使用此配置选项,而不是在配置中定义`JWT_SECRET`,以向Gitea提供包含密钥的文件的路径(示例值:`file:/etc/gitea/oauth2_jwt_secret`)。 - `JWT_SIGNING_PRIVATE_KEY_FILE`:**jwt/private.pem**:用于签署OAuth2令牌的私钥文件路径。路径相对于`APP_DATA_PATH`。仅当`JWT_SIGNING_ALGORITHM`设置为`RS256`,`RS384`,`RS512`,`ES256`,`ES384`或`ES512`时才需要此设置。文件必须包含PKCS8格式的RSA或ECDSA私钥。如果不存在密钥,则将为您创建一个4096位密钥。 - `MAX_TOKEN_LENGTH`:**32767**:从OAuth2提供者接受的令牌/cookie的最大长度。 -- `DEFAULT_APPLICATIONS`:**git-credential-oauth,git-credential-manager**:在启动时预注册用于某些服务的OAuth应用程序。有关可用选项列表,请参阅[OAuth2文档](/development/oauth2-provider.md)。 +- `DEFAULT_APPLICATIONS`:**git-credential-oauth,git-credential-manager, tea**:在启动时预注册用于某些服务的OAuth应用程序。有关可用选项列表,请参阅[OAuth2文档](/development/oauth2-provider.md)。 ## i18n (`i18n`) @@ -1331,7 +1330,7 @@ PROXY_HOSTS = *.github.com ## Actions (`actions`) -- `ENABLED`: **false**:启用/禁用操作功能 +- `ENABLED`: **true**:启用/禁用操作功能 - `DEFAULT_ACTIONS_URL`: **github**:获取操作插件的默认平台,`github`表示`https://github.com`,`self`表示当前的 Gitea 实例。 - `STORAGE_TYPE`: **local**:用于操作日志的存储类型,`local`表示本地磁盘,`minio`表示与S3兼容的对象存储服务,默认为`local`,或者使用定义为`[storage.xxx]`的其他名称。 - `MINIO_BASE_PATH`: **actions_log/**:Minio存储桶上的基本路径,仅在`STORAGE_TYPE`为`minio`时可用。 diff --git a/docs/content/administration/customizing-gitea.en-us.md b/docs/content/administration/customizing-gitea.en-us.md index 4c2d7ed0c4..d122fb4bfa 100644 --- a/docs/content/administration/customizing-gitea.en-us.md +++ b/docs/content/administration/customizing-gitea.en-us.md @@ -370,7 +370,8 @@ A full list of supported emoji's is at [emoji list](https://gitea.com/gitea/gite ## Customizing the look of Gitea -The default built-in themes are `gitea` (light), `arc-green` (dark), and `auto` (chooses light or dark depending on operating system settings). +The built-in themes are `gitea-light`, `gitea-dark`, and `gitea-auto` (which automatically adapts to OS settings). + The default theme can be changed via `DEFAULT_THEME` in the [ui](administration/config-cheat-sheet.md#ui-ui) section of `app.ini`. Gitea also has support for user themes, which means every user can select which theme should be used. @@ -384,7 +385,7 @@ To make a custom theme available to all users: Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes). -The `arc-green` theme source can be found [here](https://github.com/go-gitea/gitea/blob/main/web_src/css/themes/theme-arc-green.css). +The default theme sources can be found [here](https://github.com/go-gitea/gitea/blob/main/web_src/css/themes). If your custom theme is considered a dark theme, set the global css variable `--is-dark-theme` to `true`. This allows Gitea to adjust the Monaco code editor's theme accordingly. diff --git a/docs/content/administration/customizing-gitea.zh-cn.md b/docs/content/administration/customizing-gitea.zh-cn.md index 2babf03da7..d828704557 100644 --- a/docs/content/administration/customizing-gitea.zh-cn.md +++ b/docs/content/administration/customizing-gitea.zh-cn.md @@ -86,5 +86,6 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板 ## 更改 Gitea 外观 -Gitea 目前由两种内置主题,分别为默认 `gitea` 主题和深色主题 `arc-green`,您可以通过修改 -`app.ini` [ui](administration/config-cheat-sheet.md#ui-ui) 部分的 `DEFAULT_THEME` 的值来变更至一个可用的 Gitea 外观。 +内置主题是“gitea-light”、“gitea-dark”和“gitea-auto”(自动适应操作系统设置)。 + +默认主题可以通过 `app.ini` 的 [ui](administration/config-cheat-sheet.md#ui-ui) 部分中的 `DEFAULT_THEME` 进行更改。 diff --git a/docs/content/administration/reverse-proxies.en-us.md b/docs/content/administration/reverse-proxies.en-us.md index c141483700..fe54c67d02 100644 --- a/docs/content/administration/reverse-proxies.en-us.md +++ b/docs/content/administration/reverse-proxies.en-us.md @@ -381,3 +381,9 @@ If you really need to do so, to make Gitea works with sub-path (eg: `http://exam 1. Set `[server] ROOT_URL = http://example.com/gitea/` in your `app.ini` file. 2. Make the reverse-proxy pass `http://example.com/gitea/foo` to `http://gitea-server:3000/foo`. 3. Make sure the reverse-proxy not decode the URI, the request `http://example.com/gitea/a%2Fb` should be passed as `http://gitea-server:3000/a%2Fb`. + +## Docker / Container Registry + +The container registry uses a fixed sub-path `/v2` which can't be changed. +Even if you deploy Gitea with a different sub-path, `/v2` will be used by the `docker` client. +Therefore you may need to add an additional route to your reverse proxy configuration. diff --git a/docs/content/administration/search-engines-indexation.en-us.md b/docs/content/administration/search-engines-indexation.en-us.md index 7898e8146e..664940697d 100644 --- a/docs/content/administration/search-engines-indexation.en-us.md +++ b/docs/content/administration/search-engines-indexation.en-us.md @@ -23,7 +23,7 @@ If you don't want your repository to be visible for search engines read further. ## Block search engines indexation using robots.txt To make Gitea serve a custom `robots.txt` (default: empty 404) for top level installations, -create a file called `robots.txt` in the [`custom` folder or `CustomPath`](administration/customizing-gitea.md) +create a file with path `public/robots.txt` in the [`custom` folder or `CustomPath`](administration/customizing-gitea.md) Examples on how to configure the `robots.txt` can be found at [https://moz.com/learn/seo/robotstxt](https://moz.com/learn/seo/robotstxt). diff --git a/docs/content/administration/search-engines-indexation.zh-cn.md b/docs/content/administration/search-engines-indexation.zh-cn.md index 77ad5eca2c..904e6de11b 100644 --- a/docs/content/administration/search-engines-indexation.zh-cn.md +++ b/docs/content/administration/search-engines-indexation.zh-cn.md @@ -22,7 +22,7 @@ menu: ## 使用 robots.txt 阻止搜索引擎索引 -为了使 Gitea 为顶级安装提供自定义的`robots.txt`(默认为空的 404),请在[`custom`文件夹或`CustomPath`](administration/customizing-gitea.md)中创建一个名为 `robots.txt` 的文件。 +为了使 Gitea 为顶级安装提供自定义的`robots.txt`(默认为空的 404),请在 [`custom`文件夹或`CustomPath`](administration/customizing-gitea.md)中创建一个名为 `public/robots.txt` 的文件。 有关如何配置 `robots.txt` 的示例,请参考 [https://moz.com/learn/seo/robotstxt](https://moz.com/learn/seo/robotstxt)。 diff --git a/docs/content/contributing.fr-fr.md b/docs/content/contributing.fr-fr.md deleted file mode 100644 index 9dc4aa6ca9..0000000000 --- a/docs/content/contributing.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2021-01-22T00:00:00+02:00" -title: "Übersetzung" -slug: "contributing" -sidebar_position: 35 -toc: false -draft: false -menu: - sidebar: - name: "Übersetzung" - sidebar_position: 50 - identifier: "contributing" ---- diff --git a/docs/content/contributing.zh-tw.md b/docs/content/contributing.zh-tw.md deleted file mode 100644 index 0c715eefe4..0000000000 --- a/docs/content/contributing.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2021-01-22T00:00:00+02:00" -title: "貢獻" -slug: "contributing" -sidebar_position: 35 -toc: false -draft: false -menu: - sidebar: - name: "貢獻" - sidebar_position: 50 - identifier: "contributing" ---- diff --git a/docs/content/contributing/_index.de-de.md b/docs/content/contributing/_index.de-de.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/contributing/_index.zh-tw.md b/docs/content/contributing/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md index 921c2b0233..0d9e510e70 100644 --- a/docs/content/contributing/guidelines-frontend.en-us.md +++ b/docs/content/contributing/guidelines-frontend.en-us.md @@ -95,7 +95,7 @@ Some lint rules and IDEs also have warnings if the returned Promise is not handl ### Fetching data To fetch data, use the wrapper functions `GET`, `POST` etc. from `modules/fetch.js`. They -accept a `data` option for the content, will automatically set CSFR token and return a +accept a `data` option for the content, will automatically set CSRF token and return a Promise for a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). ### HTML Attributes and `dataset` diff --git a/docs/content/contributing/localization.zh-tw.md b/docs/content/contributing/localization.zh-tw.md deleted file mode 100644 index b045d26be2..0000000000 --- a/docs/content/contributing/localization.zh-tw.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "在地化" -slug: "localization" -sidebar_position: 70 -toc: false -draft: false -aliases: - - /zh-tw/localization -menu: - sidebar: - parent: "contributing" - name: "在地化" - sidebar_position: 70 - identifier: "localization" ---- - -# 在地化 - -我們在 [Crowdin 專案](https://crowdin.com/project/gitea)上進行在地化工作。 - -**英語系**的翻譯,可在修改[英文語言檔](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini)後提出合併請求。 - -**非英語系**的翻譯,請前往上述的 Crowdin 專案。 - -## 已支援的語言 - -上述 Crowdin 專案中列出的語言在翻譯超過 25% 後將被支援。 - -翻譯被認可後將在下次 Crowdin 同步後進入到主儲存庫,通常是在任何合併請求被合併之後。 - -這表示更改的翻譯要到下次 Gitea 發佈後才會出現。 - -如果您使用的是最新建置,它將會在同步完成、您更新後出現。 diff --git a/docs/content/development.zh-tw.md b/docs/content/development.zh-tw.md deleted file mode 100644 index 2c9d335fda..0000000000 --- a/docs/content/development.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "開發" -slug: "development" -sidebar_position: 40 -toc: false -draft: false -menu: - sidebar: - name: "開發" - sidebar_position: 40 - identifier: "development" ---- diff --git a/docs/content/development/_index.zh-tw.md b/docs/content/development/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/development/integrations.zh-tw.md b/docs/content/development/integrations.zh-tw.md deleted file mode 100644 index 9a6afad4ff..0000000000 --- a/docs/content/development/integrations.zh-tw.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -date: "2019-04-15T17:29:00+08:00" -title: "整合" -slug: "integrations" -sidebar_position: 65 -toc: false -draft: false -aliases: - - /zh-tw/integrations -menu: - sidebar: - parent: "development" - name: "整合" - sidebar_position: 65 - identifier: "integrations" ---- - -# 整合 - -Gitea 有著很棒的第三方整合社群, 以及其它有著一流支援的專案。 - -我們持續的整理一份清單以追蹤他們!請到 [awesome-gitea](https://gitea.com/gitea/awesome-gitea) 查看。 - -如果您正在找尋有關 [CI/CD](https://gitea.com/gitea/awesome-gitea#user-content-devops)、[SDK](https://gitea.com/gitea/awesome-gitea#user-content-sdk) 或是其它佈景主題,您可以在存儲庫 [awesome-gitea](https://gitea.com/gitea/awesome-gitea) 找到他們。 diff --git a/docs/content/development/migrations.zh-tw.md b/docs/content/development/migrations.zh-tw.md deleted file mode 100644 index 1dc222f379..0000000000 --- a/docs/content/development/migrations.zh-tw.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -date: "2019-04-15T17:29:00+08:00" -title: "遷移介面" -slug: "migrations-interfaces" -sidebar_position: 55 -toc: false -draft: false -aliases: - - /zh-tw/migrations-interfaces -menu: - sidebar: - parent: "development" - name: "遷移介面" - sidebar_position: 55 - identifier: "migrations-interfaces" ---- - -# 遷移功能 - -完整的遷移從 Gitea 1.9.0 開始提供。它定義了兩個介面用來從其它 Git 託管平臺遷移儲存庫資料到 Gitea,未來或許會提供遷移到其它 git 託管平臺。 -目前已實作了從 Github, Gitlab 和其它 Gitea 遷移資料。 - -Gitea 定義了一些基本物件於套件 [modules/migration](https://github.com/go-gitea/gitea/tree/master/modules/migration)。 -分別是 `Repository`, `Milestone`, `Release`, `ReleaseAsset`, `Label`, `Issue`, `Comment`, `PullRequest`, `Reaction`, `Review`, `ReviewComment`。 - -## Downloader 介面 - -從新的 Git 託管平臺遷移,有兩個新的步驟。 - -- 您必須實作一個 `Downloader`,它用來取得儲存庫資訊。 -- 您必須實作一個 `DownloaderFactory`,它用來偵測 URL 是否符合並建立上述的 `Downloader`。 - - 您需要在 `init()` 透過 `RegisterDownloaderFactory` 來註冊 `DownloaderFactory`。 - -您可以在 [downloader.go](https://github.com/go-gitea/gitea/blob/main/modules/migration/downloader.go) 中找到這些介面。 - -## Uploader 介面 - -目前只有 `GiteaLocalUploader` 被實作出來,所以我們只能通過 `Uploader` 儲存已下載的資料到本地的 Gitea 實例。 -目前尚未支援其它 Uploader。 - -您可以在 [uploader.go](https://github.com/go-gitea/gitea/blob/main/modules/migration/uploader.go) 中找到這些介面。 diff --git a/docs/content/development/oauth2-provider.en-us.md b/docs/content/development/oauth2-provider.en-us.md index 053c1d8d7e..54674f9246 100644 --- a/docs/content/development/oauth2-provider.en-us.md +++ b/docs/content/development/oauth2-provider.en-us.md @@ -86,6 +86,7 @@ Gitea creates OAuth applications for the following services by default on startu |-----------|-----------|---------| |[git-credential-oauth](https://github.com/hickford/git-credential-oauth)|Git credential helper|`a4792ccc-144e-407e-86c9-5e7d8d9c3269`| |[Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager)|Git credential helper|`e90ee53c-94e2-48ac-9358-a874fb9e0662`| +|[tea](https://gitea.com/gitea/tea)|tea|`d57cb8c4-630c-4168-8324-ec79935e18d4`| To prevent unexpected behavior, they are being displayed as locked in the UI and their creation can instead be controlled by the `DEFAULT_APPLICATIONS` parameter in `app.ini`. diff --git a/docs/content/development/oauth2-provider.zh-tw.md b/docs/content/development/oauth2-provider.zh-tw.md deleted file mode 100644 index dd9f04f03c..0000000000 --- a/docs/content/development/oauth2-provider.zh-tw.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -date: "2019-04-19:44:00+01:00" -title: "OAuth2 提供者" -slug: "oauth2-provider" -sidebar_position: 41 -toc: false -draft: false -aliases: - - /zh-tw/oauth2-provider -menu: - sidebar: - parent: "development" - name: "OAuth2 提供者" - sidebar_position: 41 - identifier: "oauth2-provider" ---- - -# OAuth2 提供者 - -**目錄** - -Gitea 支援作為 OAuth2 提供者,能讓第三方程式能在使用者同意下存取 Gitea 的資源。此功能自 1.8.0 版開始提供。 - -## Endpoint - -| Endpoint | URL | -| ---------------------- | --------------------------- | -| Authorization Endpoint | `/login/oauth/authorize` | -| Access Token Endpoint | `/login/oauth/access_token` | - -## 支援的 OAuth2 Grant - -目前 Gitea 只支援 [**Authorization Code Grant**](https://tools.ietf.org/html/rfc6749#section-1.3.1) 標準並額外支援下列擴充標準: - -- [Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636) -- [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) - -若想要讓第三方程式使用 Authorization Code Grant,需先在「設定」(`/user/settings/applications`)中註冊一個新的應用程式。 - -## Scope - -目前 Gitea 尚未支援 scope (參見 [#4300](https://github.com/go-gitea/gitea/issues/4300)),所有的第三方程式都可獲得該使用者及他所屬的組織中所有資源的存取權。 - -## 範例 - -**備註:** 此範例未使用 PKCE。 - -1. 重新導向使用者到 authorization endpoint 以獲得他同意授權存取資源: - - - ```curl - https://[YOUR-GITEA-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI& response_type=code&state=STATE - ``` - - 在設定中註冊應用程式以獲得 `CLIENT_ID`。`STATE` 是一個隨機的字串,它將在使用者授權後發送回您的應用程式。`state` 參數是選用的,但應該要用它來防止 CSRF 攻擊。 - - ![Authorization Page](/authorize.png) - - 使用者將會被詢問是否授權給您的應用程式。如果它們同意了,使用者將被重新導向到 `REDIRECT_URL`,例如: - - ```curl - https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE - ``` - -1. 使用重新導向提供的 `code`,您可以要求一個新的應用程式和 Refresh Token。Access Token Endpoint 接受 POST 請求使用 `application/json` 或 `application/x-www-form-urlencoded` 類型的請求內容,例如: - - ```curl - POST https://[YOUR-GITEA-URL]/login/oauth/access_token - ``` - - ```json - { - "client_id": "YOUR_CLIENT_ID", - "client_secret": "YOUR_CLIENT_SECRET", - "code": "RETURNED_CODE", - "grant_type": "authorization_code", - "redirect_uri": "REDIRECT_URI" - } - ``` - - 回應: - - ```json - { - "access_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjowLCJleHAiOjE1NTUxNzk5MTIsImlhdCI6MTU1NTE3NjMxMn0.0-iFsAwBtxuckA0sNZ6QpBQmywVPz129u75vOM7wPJecw5wqGyBkmstfJHAjEOqrAf_V5Z-1QYeCh_Cz4RiKug", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjoxLCJjbnQiOjEsImV4cCI6MTU1NzgwNDMxMiwiaWF0IjoxNTU1MTc2MzEyfQ.S_HZQBy4q9r5SEzNGNIoFClT43HPNDbUdHH-GYNYYdkRfft6XptJBkUQscZsGxOW975Yk6RbgtGvq1nkEcklOw" - } - ``` - - `CLIENT_SECRET` 是產生給此應用程式的唯一密鑰。請記住該密鑰只會在您於 Gitea 建立/註冊應用程式時出現一次。若您遺失密鑰,您必須在該應用程式的設定中重新產生密鑰。 - - `access_token` 請求中的 `REDIRECT_URI` 必須符合 `authorize` 請求中的 `REDIRECT_URI`。 - -1. 發送 [API requests](development/api-usage.md#oauth2-provider) 時使用 `access_token` 以存取使用者的資源。 diff --git a/docs/content/help.fr-fr.md b/docs/content/help.fr-fr.md deleted file mode 100644 index cb41ad6487..0000000000 --- a/docs/content/help.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-01-20T15:00:00+08:00" -title: "Aide" -slug: "help" -sidebar_position: 5 -toc: false -draft: false -menu: - sidebar: - name: "Aide" - sidebar_position: 100 - identifier: "help" ---- diff --git a/docs/content/help.zh-tw.md b/docs/content/help.zh-tw.md deleted file mode 100644 index 840ea100e8..0000000000 --- a/docs/content/help.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-01-20T15:00:00+08:00" -title: "幫助" -slug: "help" -sidebar_position: 5 -toc: false -draft: false -menu: - sidebar: - name: "幫助" - sidebar_position: 100 - identifier: "help" ---- diff --git a/docs/content/help/_index.zh-tw.md b/docs/content/help/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/help/faq.en-us.md b/docs/content/help/faq.en-us.md index c83f8549e2..b4f5171bcf 100644 --- a/docs/content/help/faq.en-us.md +++ b/docs/content/help/faq.en-us.md @@ -181,7 +181,7 @@ Use [Fail2Ban](administration/fail2ban-setup.md) to monitor and stop automated l ## How to add/use custom themes -Gitea supports three official themes right now, `gitea` (light), `arc-green` (dark), and `auto` (automatically switches between the previous two depending on operating system settings). +Gitea supports three official themes right now, `gitea-light`, `gitea-dark`, and `gitea-auto` (automatically switches between the previous two depending on operating system settings). To add your own theme, currently the only way is to provide a complete theme (not just color overrides) As an example, let's say our theme is `arc-blue` (this is a real theme, and can be found [in this issue](https://github.com/go-gitea/gitea/issues/6011)) diff --git a/docs/content/help/faq.zh-cn.md b/docs/content/help/faq.zh-cn.md index 36610db8e0..ff53fc1d03 100644 --- a/docs/content/help/faq.zh-cn.md +++ b/docs/content/help/faq.zh-cn.md @@ -185,7 +185,7 @@ Gitea不提供内置的Pages服务器。您需要一个专用的域名来提供 ## 如何添加/使用自定义主题 -Gitea 目前支持三个官方主题,分别是 `gitea`(亮色)、`arc-green`(暗色)和 `auto`(根据操作系统设置自动切换前两个主题)。 +Gitea 目前支持三个官方主题,分别是 `gitea-light`、`gitea-dark` 和 `gitea-auto`(根据操作系统设置自动切换前两个主题)。 要添加自己的主题,目前唯一的方法是提供一个完整的主题(不仅仅是颜色覆盖)。 假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到) diff --git a/docs/content/help/support.zh-tw.md b/docs/content/help/support.zh-tw.md deleted file mode 100644 index 511b362417..0000000000 --- a/docs/content/help/support.zh-tw.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -date: "2018-05-21T15:00:00+00:00" -title: "取得協助" -slug: "support" -sidebar_position: 20 -toc: false -draft: false -aliases: - - /zh-tw/seek-help -menu: - sidebar: - parent: "help" - name: "取得協助" - sidebar_position: 20 - identifier: "support" ---- - -# 取得協助 - -- [Discord 聊天室](https://discord.gg/Gitea) -- [Discourse 討論區](https://discourse.gitea.io/) - -**備註:** 尋求支援時,若能先備妥下列資訊將可讓提供協助的人能快速地分析您的問題: - -1. 您的 `app.ini` (必要時清除掉任何機密資訊) -2. `gitea.log` (以及任何有關的日誌檔案) - - 例:如果錯誤和資料庫相關,提供 `xorm.log` 可能會有幫助 -3. 您看到的任何錯誤訊息 -4. 儘可能地在 [try.gitea.io](https://try.gitea.io) 觸發您的問題並記下步驟,以便其他人能重現那個問題。 - - 這將讓我們更有機會快速地找出問題的根源並解決它。 -5. 堆棧跟踪,[請參考英文文檔](https://docs.gitea.com/help/support) - -## 錯誤回報 - -如果您發現錯誤,請到 [GitHub 的 Issue](https://github.com/go-gitea/gitea/issues) 回報。 diff --git a/docs/content/index.de-de.md b/docs/content/index.de-de.md deleted file mode 100644 index 7f0f611c25..0000000000 --- a/docs/content/index.de-de.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -date: "2023-01-07T22:03:00+01:00" -title: "Dokumentation" -slug: / -sidebar_position: 10 -toc: false -draft: false ---- - -# Was ist Gitea? - -Gitea ist ein einfacher, selbst gehosteter Git-Service. Änlich wie GitHub, Bitbucket oder GitLab. - -Gitea ist ein [Gogs](http://gogs.io)-Fork. - -## Ziele - -* Einfach zu installieren -* Plattformübergreifend -* Leichtgewichtig -* Quelloffen - -## System Voraussetzungen - -* Ein Raspberry Pi 3 ist leistungsstark genug, um Gitea für kleine Belastungen laufen zu lassen. -* 2 CPU Kerne und 1GB RAM sind für kleine Teams/Projekte ausreichend. -* Gitea sollte unter einem seperaten nicht-root Account auf UNIX-Systemen ausgeführt werden. - * Achtung: Gitea verwaltet die `~/.ssh/authorized_keys` Datei. Gitea unter einem normalen Benutzer auszuführen könnte dazu führen, dass dieser sich nicht mehr anmelden kann. -* [Git](https://git-scm.com/) Version 2.0 oder aktueller wird benötigt. - * Wenn Git >= 2.1.2 und [Git LFS](https://git-lfs.github.com/) vorhanden ist, dann wird Git LFS Support automatisch für Gitea aktiviert. - * Wenn Git >= 2.18, dann wird das Rendern von Commit-Graphen automatisch aktiviert. - -## Browser Unterstützung - -* Die neuesten zwei Versionen von Chrome, Firefox, Safari und Edge -* Firefox ESR diff --git a/docs/content/index.fr-fr.md b/docs/content/index.fr-fr.md deleted file mode 100755 index 60a9dfb6a3..0000000000 --- a/docs/content/index.fr-fr.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Documentation" -slug: / -sidebar_position: 10 -toc: false -draft: false ---- - -# A propos de Gitea - -Gitea est un service Git auto-hébergé très simple à installer et à utiliser. Il est similaire à GitHub, Bitbucket ou Gitlab. Le développement initial provient sur [Gogs] (http://gogs.io), mais nous l'avons forké puis renommé Gitea. Si vous souhaitez en savoir plus sur les raisons pour lesquelles nous avons fait cela, lisez [cette publication] (https://blog.gitea.com/welcome-to-gitea/) sur le blog. - -## Objectif - -Le but de ce projet est de fournir de la manière la plus simple, la plus rapide et sans complication un service Git auto-hébergé. Grâce à Go, cela peut se faire via un binaire indépendant fonctionnant sur toutes les plateformes que Go prend en charge, y compris Linux, macOS et Windows, même sur des architectures comme ARM ou PowerPC. - -## Fonctionalités - -- Tableau de bord de l'utilisateur - - Choix du contexte (organisation ou utilisateur actuel) - - Chronologie de l'activité - - Révisions (_Commits_) - - Tickets - - Demande d'ajout (_Pull request_) - - Création de dépôts - - Liste des dépôts - - Liste de vos organisations - - Liste des dépôts miroires -- Tableau de bord des tickets - - Choix du contexte (organisation ou utilisateur actuel) - - Filtres - - Ouvert - - Fermé - - Vos dépôts - - Tickets assignés - - Vos tickets - - Dépôts - - Options de tri - - Plus vieux - - Dernière mise à jour - - Nombre de commentaires -- Tableau de bord des demandes d'ajout - - Identique au tableau de bord des tickets -- Types de dépôt - - Miroire - - Normal - - Migré -- Notifications (courriel et web) - - Lu - - Non lu - - Épinglé -- Page d'exploration - - Utilisateurs - - Dépôts - - Organisations - - Moteur de recherche -- Interface personnalisables -- Fichiers publiques remplaçables (logo, css, etc) -- Protection CSRF et XSS -- Support d'HTTPS -- Configuration des types et de la taille maximale des fichiers téléversés -- Journalisation (_Log_) -- Configuration - - Base de données - - MySQL - - PostgreSQL - - SQLite3 - - MSSQL - - [TiDB](https://github.com/pingcap/tidb) (MySQL protocol) - - Fichier de configuration - - Voir [ici](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) - - Panel d'administration - - Statistiques - - Actions - - Suppression des comptes inactifs - - Suppression des dépôts archivés - - Suppression des dépôts pour lesquels il manque leurs fichiers - - Exécution du _garbage collector_ sur les dépôts - - Ré-écriture des clefs SSH - - Resynchronisation des hooks - - Recreation des dépôts manquants - - Status du server - - Temps de disponibilité - - Mémoire - - Nombre de goroutines - - et bien plus... - - Gestion des utilisateurs - - Recherche - - Tri - - Dernière connexion - - Méthode d'authentification - - Nombre maximum de dépôts - - Désactivation du compte - - Permissions d'administration - - Permission pour crééer des hooks - - Permission pour crééer des organisations - - Permission pour importer des dépôts - - Gestion des organisations - - Membres - - Équipes - - Avatar - - Hooks - - Gestion des depôts - - Voir toutes les informations pour un dépôt donné et gérer tous les dépôts - - Méthodes d'authentification - - OAuth - - PAM - - LDAP - - SMTP - - Visualisation de la configuration - - Tout ce que contient le fichier de configuration - - Alertes du système - - Quand quelque chose d'inattendu survient - - Surveillance - - Processus courrants - - Tâches CRON - - Mise à jour des dépôts miroires - - Vérification de l'état des dépôts - - Vérification des statistiques des dépôts - - Nettoyage des anciennes archives - - Variables d'environement - - Options de ligne de commande -- Internationalisation ([21 langues](https://github.com/go-gitea/gitea/tree/master/options/locale)) -- Courriel - - Notifications - - Confirmation d'inscription - - Ré-initialisation du mot de passe -- Support de _reverse proxy_ - - _subpaths_ inclus -- Utilisateurs - - Profil - - Nom - - Prénom - - Courriel - - Site internet - - Date de création - - Abonnés et abonnements - - Organisations - - Dépôts - - Activité - - Dépôts suivis - - Paramètres - - Identiques au profil avec en plus les éléments ci-dessous - - Rendre l'adresse de courriel privée - - Avatar - - Gravatar - - Libravatar - - Personnalisé - - Mot de passe - - Courriels multiples - - Clefs SSH - - Applications connectées - - Authentification à double facteurs - - Identités OAuth2 attachées - - Suppression du compte -- Dépôts - - Clone à partir de SSH / HTTP / HTTPS - - Git LFS - - Suivre, Voter, Fork - - Voir les personnes qui suivent, les votes et les forks - - Code - - Navigation entre les branches - - Création ou téléversement de fichier depuis le navigateur - - URLs pour clôner le dépôt - - Téléchargement - - ZIP - - TAR.GZ - - Édition en ligne - - Éditeur Markdown - - Éditeur de texte - - Coloration syntaxique - - Visualisation des Diffs - - Visualisation - - Possibilité de choisir où sauvegarder la révision - - Historiques des fichiers - - Suppression de fichiers - - Voir le fichier brut - - Tickets - - Modèle de ticket - - Jalons - - Étiquettes - - Affecter des tickets - - Filtres - - Ouvert - - Ferme - - Personne assignée - - Créer par vous - - Qui vous mentionne - - Tri - - Plus vieux - - Dernière mise à jour - - Nombre de commentaires - - Moteur de recherche - - Commentaires - - Joindre des fichiers - - Demande d’ajout (_Pull request_) - - Les mêmes fonctionnalités que pour les tickets - - Révisions (_Commits_) - - Representation graphique des révisions - - Révisions par branches - - Moteur de recherche - - Voir les différences - - Voir les numéro de révision SHA - - Voir l'auteur - - Naviguer dans les fichiers d'une révision donnée - - Publication - - Pièces jointes - - Titre - - Contenu - - Suppression - - Définir comme une pré-publication - - Choix de la branche - - Wiki - - Import - - Éditeur Markdown - - Paramètres - - Options - - Nom - - Description - - Privé / Publique - - Site internet - - Wiki - - Activé / Désactivé - - Interne / externe - - Tickets - - Activé / Désactivé - - Interne / externe - - URL personnalisable pour une meilleur intégration avec un gestionnaire de tickets externe - - Activer / désactiver les demandes d'ajout (_Pull request_) - - Transfert du dépôt - - Suppression du wiki - - Suppression du dépôt - - Collaboration - - Lecture / Écriture / Administration - - Branches - - Branche par défaut - - Protection - - Webhooks - - Git hooks - - Clefs de déploiement - -## Configuration requise - -- Un simple Raspberry Pi est assez puissant pour les fonctionnalités de base. -- Un processeur double coeurs et 1Gb de RAM est une bonne base pour une utilisation en équipe. -- Gitea est censé être exécuté avec un compte utilisateur dédié et non root, aucun autre mode de fonctionnement n'est pris en charge. (**NOTE**: Dans le cas où vous l'exécutez avec votre propre compte d'utilisateur et que le serveur SSH intégré est désactivé, Gitea modifie le fichier `~ /.ssh /authorized_keys` afin que vous ne soyez **plus capable** de vous connecter interactivement). - -## Navigateurs supportés - -- Chrome, Firefox, Safari, Edge - -## Composants - -- Framework web : [Chi](http://github.com/go-chi/chi) -- ORM: [XORM](https://xorm.io) -- Interface graphique : - - [jQuery](https://jquery.com) - - [Fomantic UI](https://fomantic-ui.com) - - [Vue3](https://vuejs.org) - - [CodeMirror](https://codemirror.net) - - [EasyMDE](https://github.com/Ionaru/easy-markdown-editor) - - [Monaco Editor](https://microsoft.github.io/monaco-editor) - - ... (package.json) -- Connecteurs de base de données : - - [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) - - [github.com/lib/pq](https://github.com/lib/pq) - - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) - - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) - -## Logiciels et services - -- [Drone](https://github.com/drone/drone) (Intégration continue) diff --git a/docs/content/index.zh-tw.md b/docs/content/index.zh-tw.md deleted file mode 100644 index 41a5e7e067..0000000000 --- a/docs/content/index.zh-tw.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -date: "2016-11-08T16:00:00+02:00" -title: "文件" -slug: / -sidebar_position: 10 -toc: false -draft: false ---- - -# 關於 Gitea - -Gitea 是一個可自行託管的 Git 服務。你可以拿 GitHub、Bitbucket 或 Gitlab 來比較看看。 -Gitea 是從 [Gogs](http://gogs.io) Fork 出來的,請閱讀部落格文章 [Gitea 公告](https://blog.gitea.com/welcome-to-gitea/)以了解我們 Fork 的理由。 - -## 目標 - -本專案的首要目標是建立一個容易安裝,執行快速,安装和使用體驗良好的自建 Git 服務。我們採用 GO 為後端語言,Go 可以產生各平台使用的執行檔。它支援 Linux、macOS 和 Windows 外,處理器架構包含 amd64、i386、ARM 和 PowerPC 等。 - -## 功能 - -- 代碼託管:Gitea 支援建立和管理存儲庫、瀏覽提交歷史和程式碼檔案、審查和合併程式碼提交、管理協作者、處理分支等。它還支援許多常見的 Git 功能,如標籤、Cherry-pick、鉤子、集成協作工具等。 - -- 輕量級和快速:Gitea 的設計目標之一就是輕量級和快速響應。與某些大型代碼託管平台不同,它保持了精簡,在速度方面表現出色,適用於資源有限的伺服器環境。由於其輕量級設計,Gitea 的資源消耗相對較低,在資源受限的環境中表現出色。 - -- 易於部署和維護:它可以輕鬆地部署在各種伺服器上,無需複雜的配置或依賴。這使得個人開發者或小團隊可以方便地設置和管理自己的 Git 服務。 - -- 安全性:Gitea 強調安全性,提供用戶權限管理、訪問控制列表等功能,確保程式碼和數據的安全性。 - -- 代碼審查:代碼審查同時支援拉取請求工作流和 AGit 工作流。審查者可以在線瀏覽程式碼並提供審查意見或反饋。提交者可以接收審查意見並在線回覆或修改程式碼。代碼審查可以幫助個人和組織提升程式碼質量。 - -- CI/CD:Gitea Actions 支援 CI/CD 功能,與 GitHub Actions 相容。用戶可以使用熟悉的 YAML 格式編寫工作流程,並重複使用各種現有的 Actions 插件。Actions 插件支援從任何 Git 網站下載。 - -- 專案管理:Gitea 通過看板和工單來追蹤一個專案的需求、功能和錯誤。工單支援分支、標籤、里程碑、指派、時間追蹤、到期日期、依賴關係等功能。 - -- 制品庫:Gitea 支援超過 20 種不同類型的公有或私有軟體包管理,包括:Cargo、Chef、Composer、Conan、Conda、Container、Helm、Maven、npm、NuGet、Pub、PyPI、RubyGems、Vagrant 等。 - -- 開源社區支援:Gitea 是一個基於 MIT 許可證的開源專案,擁有活躍的開源社區,能夠持續進行開發和改進,同時也積極接受社區貢獻,保持了平台的更新和創新。 - -- 多語言支援:Gitea 提供多種語言界面,適應全球範圍內的用戶,促進了國際化和本地化。 - -更多功能特性:詳見:https://docs.gitea.com/installation/comparison#general-features - -## 系統需求 - -- Raspberry Pi 3 的效能足夠讓 Gitea 承擔小型工作負載。 -- 雙核心 CPU 和 1GB 記憶體通常足以應付小型團隊/專案。 -- 在類 UNIX 系統上, 應該以專用的非 root 系統帳號來執行 Gitea。 - - 備註:Gitea 管理著 `~/.ssh/authorized_keys` 檔案。以一般身份使用者執行 Gitea 可能會破壞該使用者的登入能力。 - -- [Git](https://git-scm.com/) 的最低需求為 2.0 或更新版本。 - - 當 git 版本 >= 2.1.2 時,可啟用 Git [large file storage](https://git-lfs.github.com/)。 - - 當 git 版本 >= 2.18 時,將自動啟用 Git 提交線圖渲染。 - -## 瀏覽器支援 - -- 最近 2 個版本的 Chrome, Firefox, Safari, Edge -- Firefox ESR - -## 元件 - -- Web 框架: [Chi](http://github.com/go-chi/chi) -- ORM: [XORM](https://xorm.io) -- UI 元件: - - [jQuery](https://jquery.com) - - [Fomantic UI](https://fomantic-ui.com) - - [Vue3](https://vuejs.org) - - [CodeMirror](https://codemirror.net) - - [EasyMDE](https://github.com/Ionaru/easy-markdown-editor) - - [Monaco Editor](https://microsoft.github.io/monaco-editor) - - ... (package.json) -- 資料庫驅動程式: - - [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) - - [github.com/lib/pq](https://github.com/lib/pq) - - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) - - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) - -## 集成支持 - -請訪問 [Awesome Gitea](https://gitea.com/gitea/awesome-gitea/) 獲得更多的第三方集成支持 diff --git a/docs/content/installation.fr-fr.md b/docs/content/installation.fr-fr.md deleted file mode 100644 index 1466e03e18..0000000000 --- a/docs/content/installation.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation" -slug: "installation" -sidebar_position: 10 -toc: false -draft: false -menu: - sidebar: - name: "Installation" - sidebar_position: 10 - identifier: "installation" ---- diff --git a/docs/content/installation.zh-tw.md b/docs/content/installation.zh-tw.md deleted file mode 100644 index 48b0983f2a..0000000000 --- a/docs/content/installation.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "安裝" -slug: "installation" -sidebar_position: 10 -toc: false -draft: false -menu: - sidebar: - name: "安裝" - sidebar_position: 10 - identifier: "installation" ---- diff --git a/docs/content/installation/_index.fr-fr.md b/docs/content/installation/_index.fr-fr.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/installation/_index.zh-tw.md b/docs/content/installation/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/installation/comparison.en-us.md b/docs/content/installation/comparison.en-us.md index 80fa9b7054..e48410ba6f 100644 --- a/docs/content/installation/comparison.en-us.md +++ b/docs/content/installation/comparison.en-us.md @@ -33,117 +33,117 @@ _Symbols used in table:_ ## General Features -| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ------------------------------------------------ | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | -| Open source and free | ✓ | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | -| Low RAM/ CPU usage | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | -| Multiple database support | ✓ | ✓ | ✘ | ⁄ | ⁄ | ✓ | ✓ | -| Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | -| Easy upgrades | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | -| Telemetry | **✘** | ✘ | ✓ | ✓ | ✓ | ✓ | ? | -| Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? | -| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? | -| Extensive API | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Built-in Package/Container Registry | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Sync commits to an external repo (push mirror) | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | -| Sync commits from an external repo (pull mirror) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ? | -| Light and Dark Theme | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ? | -| Custom Theme Support | ✓ | ✓ | ✘ | ✘ | ✘ | ✓ | ✘ | -| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| CSV support | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? | -| 'GitHub / GitLab pages' | [⚙️][gitea-pages-server], [⚙️][gitea-caddy-plugin] | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Repo-specific wiki (as a repo itself) | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ | -| Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| RSS Feeds | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ✘ | -| Built-in CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Subgroups: groups within groups | [✘](https://github.com/go-gitea/gitea/issues/1872) | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | -| Interaction with other instances | [/](https://github.com/go-gitea/gitea/issues/18240) | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | -| Mermaid diagrams in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Math syntax in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | RhodeCode EE | +| ------------------------------------------------ | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | ------------ | +| Open source and free | ✓ | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ✓ | +| Low RAM/ CPU usage | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | +| Multiple database support | ✓ | ✓ | ✘ | ⁄ | ⁄ | ✓ | ✓ | ✓ | +| Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ✓ | +| Easy upgrades | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | ✓ | +| Telemetry | **✘** | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ✘ | ✘ | +| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✓ | +| Extensive API | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Built-in Package/Container Registry | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Sync commits to an external repo (push mirror) | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | ✓ | +| Sync commits from an external repo (pull mirror) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | ✓ | +| Light and Dark Theme | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Custom Theme Support | ✓ | ✓ | ✘ | ✘ | ✘ | ✓ | ✓ | ✓ | +| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| CSV support | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ✘ | ✘ | +| 'GitHub / GitLab pages' | [⚙️][gitea-pages-server], [⚙️][gitea-caddy-plugin] | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Repo-specific wiki (as a repo itself) | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ | ✘ | +| Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| RSS Feeds | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ✓ | ✓ | +| Built-in CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Subgroups: groups within groups | [✘](https://github.com/go-gitea/gitea/issues/1872) | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | ✓ | +| Interaction with other instances | [/](https://github.com/go-gitea/gitea/issues/18240) | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | +| Mermaid diagrams in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Math syntax in Markdown | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | ✓ | ## Code management -| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ------------------------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | -| Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Global code search | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | -| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Group Milestones | [✘](https://github.com/go-gitea/gitea/issues/14622) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| Granular user roles (Code, Issues, Wiki, …) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| Verified Committer | ⁄ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | -| GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| SSH Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ? | -| Reject unsigned commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Migrating repos from other services | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Web code editor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Commit graph | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Template Repositories | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ | -| Git Blame | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Visual comparison of image changes | ✓ | ✘ | ✓ | ? | ? | ? | ? | +| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | RhodeCode EE | +| ------------------------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | ------------ | +| Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Global code search | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | +| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Group Milestones | [✘](https://github.com/go-gitea/gitea/issues/14622) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Granular user roles (Code, Issues, Wiki, …) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Verified Committer | ⁄ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | ✘ | +| GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| SSH Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | ✘ | +| Reject unsigned commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Migrating repos from other services | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Web code editor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Commit graph | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Template Repositories | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ | ✘ | +| Git Blame | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Visual comparison of image changes | ✓ | ✘ | ✓ | ? | ? | ? | ✘ | ✘ | ## Issue Tracker -| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ----------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | -| Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ | -| Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Multiple assignees for issues | ✓ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | -| Related issues | ✘ | ✘ | ⁄ | ✓ | ✓ | ✘ | ✘ | -| Confidential issues | [✘](https://github.com/go-gitea/gitea/issues/3217) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Lock Discussion | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Issue Boards (Kanban) | [/](https://github.com/go-gitea/gitea/issues/14710) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| Create branch from issue | [✘](https://github.com/go-gitea/gitea/issues/20226) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| Convert comment to new issue | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Global issue search | [/](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Issue dependency | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | -| Create issue via email | [✘](https://github.com/go-gitea/gitea/issues/6226) | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | -| Service Desk | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | +| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | RhodeCode EE | +| ----------------------------- | --------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | ------------ | +| Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | / | ✘ | ✘ | +| Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Multiple assignees for issues | ✓ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | +| Related issues | ✘ | ✘ | ⁄ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Confidential issues | [✘](https://github.com/go-gitea/gitea/issues/3217) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Lock Discussion | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Issue Boards (Kanban) | [/](https://github.com/go-gitea/gitea/issues/14710) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Create branch from issue | [✘](https://github.com/go-gitea/gitea/issues/20226) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Convert comment to new issue | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Global issue search | [/](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Issue dependency | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | +| Create issue via email | [✘](https://github.com/go-gitea/gitea/issues/6226) | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Service Desk | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | ✘ | ## Pull/Merge requests -| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ----------------------------------------------- | -------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | -| Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Squash merging | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Rebase merging | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Pull/Merge request inline comments | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Pull/Merge request approval | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Pull/Merge require approval | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | -| Pull/Merge multiple reviewers | ✓ | ✓ | ✓ | ✘ | ✓ | ✓ | ✓ | -| Merge conflict resolution | [✘](https://github.com/go-gitea/gitea/issues/9014) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Revert specific commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Cherry-picking changes | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| Download Patch | ✓ | ✘ | ✓ | ✓ | ✓ | / | ✘ | -| Merge queues | ✘ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | +| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | RhodeCode EE | +| ----------------------------------------------- | -------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | ------------ | +| Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Squash merging | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Rebase merging | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Pull/Merge request inline comments | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Pull/Merge request approval | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Pull/Merge require approval | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | +| Pull/Merge multiple reviewers | ✓ | ✓ | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | +| Merge conflict resolution | [✘](https://github.com/go-gitea/gitea/issues/9014) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Revert specific commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | +| Cherry-picking changes | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | ✓ | +| Download Patch | ✓ | ✘ | ✓ | ✓ | ✓ | / | ✓ | ✓ | +| Merge queues | ✘ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ## 3rd-party integrations -| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ---------------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | -| Webhooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | -| LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| SAML 2.0 service provider | [✘](https://github.com/go-gitea/gitea/issues/5512) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| OpenID Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | -| OAuth 2.0 integration (external authorization) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✓ | -| Act as OAuth 2.0 provider | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Two factor authentication (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | -| Integration with the most common services | ✓ | / | ⁄ | ✓ | ✓ | ⁄ | ✓ | -| Incorporate external CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | RhodeCode EE | +| ---------------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | ------------ | +| Webhooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | +| LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✓ | +| SAML 2.0 service provider | [✘](https://github.com/go-gitea/gitea/issues/5512) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✓ | +| OpenID Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | ✓ | +| OAuth 2.0 integration (external authorization) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✘ | ✓ | +| Act as OAuth 2.0 provider | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | +| Two factor authentication (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✓ | +| Integration with the most common services | ✓ | / | ⁄ | ✓ | ✓ | ⁄ | ✓ | ✓ | +| Incorporate external CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | [gitea-caddy-plugin]: https://github.com/42wim/caddy-gitea [gitea-pages-server]: https://codeberg.org/Codeberg/pages-server diff --git a/docs/content/installation/comparison.zh-tw.md b/docs/content/installation/comparison.zh-tw.md deleted file mode 100644 index 01a1035daf..0000000000 --- a/docs/content/installation/comparison.zh-tw.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -date: "2018-05-07T13:00:00+02:00" -title: "比較 Gitea 和其它自託管 Git 服務" -slug: "comparison" -sidebar_position: 5 -toc: false -draft: false -aliases: - - /zh-tw/comparison -menu: - sidebar: - parent: "installation" - name: "比較" - sidebar_position: 5 - identifier: "comparison" ---- - -# 比較 Gitea 和其它自託管 Git 服務 - -**目錄** - -為了幫助您判斷 Gitea 是否適合您的需求,這裡列出了它和其它自託管 Git 服務的比較。 - -請注意我們不會經常檢查其它產品的功能異動,所以這份清單可能過期,如果您在下方表格中找到需要更新的資料,請在 [GitHub 的 Issue](https://github.com/go-gitea/gitea/issues) 回報。 - -表格中使用的符號: - -- ✓ - 支援 - -- ⁄ - 有限度的支援 - -- ✘ - 不支援 - -- _⚙️ - 由第三方服務或外掛程式支援_ - -## 一般功能 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ------------------------ | -------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | -| 免費及開放原始碼 | ✓ | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | -| 低資源使用 (RAM/CPU) | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | -| 支援多種資料庫 | ✓ | ✓ | ✘ | ⁄ | ⁄ | ✓ | ✓ | -| 支援多種作業系統 | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | -| 簡單的升級程序 | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | -| 支援 Markdown | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 支援 Orgmode | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ? | -| 支援 CSV | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? | -| 支援第三方渲染工具 | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? | -| Git 驅動的靜態頁面 | [⚙️][gitea-pages-server], [⚙️][gitea-caddy-plugin] | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Git 驅動的整合 wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 部署 Token | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 有寫入權限的儲存庫 Token | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | -| 內建 Container Registry | [✘](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 對外部 Git 鏡像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | -| FIDO (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 內建 CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 子群組: 群組中的群組 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | - -## 程式碼管理 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ----------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | -| 儲存庫主題描述 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 儲存庫程式碼搜尋 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 全域程式碼搜尋 | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | -| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ⁄ | ✓ | -| 群組里程碑 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 精細的使用者權限(程式碼, 問題, Wiki 等) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 驗證提交者 | ⁄ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | -| GPG 簽署提交 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 拒絕未經簽署的提交 | [✓](https://github.com/go-gitea/gitea/pull/9708) | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | -| 儲存庫動態頁 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 分支管理 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 建立新分支 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 網頁程式碼編輯器 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 提交線圖 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 儲存庫範本 | [✓](https://github.com/go-gitea/gitea/pull/8768) | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ | - -## 問題追蹤器 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| -------------------- | -------------------------------------------------- | --------------------------------------------- | --------- | ----------------------------------------------------------------------- | --------- | --------- | ------------ | -| 問題追蹤器 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 問題範本 | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 標籤 | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 時間追蹤 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 指派問題給多個成員 | ✓ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | -| 相關問題 | ✘ | ✘ | ⁄ | [✓](https://docs.gitlab.com/ce/user/project/issues/related_issues.html) | ✓ | ✘ | ✘ | -| 機密問題 | [✘](https://github.com/go-gitea/gitea/issues/3217) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 對留言的反應 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 鎖定對話 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 批次處理問題 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 問題看板(看板方法) | [✓](https://github.com/go-gitea/gitea/pull/8346) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 從問題建立新分支 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 問題搜尋 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 全域問題搜尋 | [✘](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 問題相依 | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | -| 從電子郵件建立問題 | [✘](https://github.com/go-gitea/gitea/issues/6226) | [✘](https://github.com/gogs/gogs/issues/2602) | ✘ | ✓ | ✓ | ✓ | ✘ | -| 服務台 | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | [✓](https://gitlab.com/groups/gitlab-org/-/epics/3103) | ✓ | ✘ | ✘ | - -## 拉取/合併請求 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| -------------------------- | -------------------------------------------------- | ---- | --------- | --------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------ | ------------ | -| 拉取/合併請求 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Squash 合併 | ✓ | ✘ | ✓ | [✓](https://docs.gitlab.com/ce/user/project/merge_requests/squash_and_merge.html) | ✓ | ✓ | ✓ | -| Rebase 合併 | ✓ | ✓ | ✓ | ✘ | ⁄ | ✘ | ✓ | -| 拉取/合併請求的行內留言 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 拉取/合併請求的核可 | ✓ | ✘ | ⁄ | ✓ | ✓ | ✓ | ✓ | -| 解決合併衝突 | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 限制某些使用者的推送及合併 | ✓ | ✘ | ✓ | ⁄ | ✓ | ✓ | ✓ | -| 還原指定的提交或合併請求 | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 拉取/合併請求範本 | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Cherry-picking 變更 | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 下載 Patch | ✓ | ✘ | ✓ | ✓ | ✓ | [/](https://jira.atlassian.com/plugins/servlet/mobile#issue/BCLOUD-8323) | ✘ | - -## 第三方整合 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | -| 支援 Webhook | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 自訂 Git Hook | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 整合 AD / LDAP | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 支援多重 LDAP / AD 伺服器 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | -| 同步 LDAP 使用者 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| SAML 2.0 service provider | [✘](https://github.com/go-gitea/gitea/issues/5512) | [✘](https://github.com/gogs/gogs/issues/1221) | ✓ | ✓ | ✓ | ✓ | ✘ | -| 支援 OpenId Connect | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | -| 整合 OAuth 2.0 (外部驗證) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✓ | -| 成為 OAuth 2.0 提供者 | [✓](https://github.com/go-gitea/gitea/pull/5378) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 兩步驟驗證 (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 整合 Mattermost/Slack | ✓ | ✓ | ⁄ | ✓ | ✓ | ⁄ | ✓ | -| 整合 Discord | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 整合 Microsoft Teams | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 顯示外部 CI/CD 狀態 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | - -[gitea-caddy-plugin]: https://github.com/42wim/caddy-gitea -[gitea-pages-server]: https://codeberg.org/Codeberg/pages-server diff --git a/docs/content/installation/database-preparation.en-us.md b/docs/content/installation/database-preparation.en-us.md index 21360fa4d2..25b163793e 100644 --- a/docs/content/installation/database-preparation.en-us.md +++ b/docs/content/installation/database-preparation.en-us.md @@ -17,13 +17,15 @@ menu: # Database Preparation -You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter. +You need a database to use Gitea. Gitea supports PostgreSQL (>= 12), MySQL (>= 8.0), MariaDB, SQLite, and MSSQL (>= 2012 SP4). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter. + +If you use an unsupported database version, please [get in touch](/help/support) with us for information on our Extended Support Contracts. We can provide testing and support for older databases and integrate those fixes into the Gitea codebase. Database instance can be on same machine as Gitea (local database setup), or on different machine (remote database). Note: All steps below requires that the database engine of your choice is installed on your system. For remote database setup, install the server application on database instance and client program on your Gitea server. The client program is used to test connection to the database from Gitea server, while Gitea itself use database driver provided by Go to accomplish the same thing. In addition, make sure you use same engine version for both server and client for some engine features to work. For security reason, protect `root` (MySQL) or `postgres` (PostgreSQL) database superuser with secure password. The steps assumes that you run Linux for both database and Gitea servers. -## MySQL +## MySQL/MariaDB 1. For remote database setup, you will need to make MySQL listen to your IP address. Edit `bind-address` option on `/etc/mysql/my.cnf` on database instance to: @@ -45,7 +47,7 @@ Note: All steps below requires that the database engine of your choice is instal ```sql SET old_passwords=0; - CREATE USER 'gitea' IDENTIFIED BY 'gitea'; + CREATE USER 'gitea'@'%' IDENTIFIED BY 'gitea'; ``` For remote database: @@ -182,7 +184,7 @@ If the communication between Gitea and your database instance is performed throu - On the database server certificate, one of `Subject Alternative Name` or `Common Name` entries must be the fully-qualified domain name (FQDN) of the database instance (e.g. `db.example.com`). On the database client certificate, one of the entries mentioned above must contain the database username that Gitea will be using to connect. - You need domain name mappings of both Gitea and database servers to their respective IP addresses. Either set up DNS records for them or add local mappings to `/etc/hosts` (`%WINDIR%\System32\drivers\etc\hosts` in Windows) on each system. This allows the database connections to be performed by domain name instead of IP address. See documentation of your system for details. -### PostgreSQL +### PostgreSQL TLS The PostgreSQL driver used by Gitea supports two-way TLS. In two-way TLS, both database client and server authenticate each other by sending their respective certificates to their respective opposite for validation. In other words, the server verifies client certificate, and the client verifies server certificate. @@ -250,7 +252,7 @@ The PostgreSQL driver used by Gitea supports two-way TLS. In two-way TLS, both d You should be prompted to enter password for the database user, and then be connected to the database. -### MySQL +### MySQL/MariaDB TLS While the MySQL driver used by Gitea also supports two-way TLS, Gitea currently supports only one-way TLS. See issue #10828 for details. diff --git a/docs/content/installation/database-preparation.zh-cn.md b/docs/content/installation/database-preparation.zh-cn.md index b5e8b73f1f..b58344133e 100644 --- a/docs/content/installation/database-preparation.zh-cn.md +++ b/docs/content/installation/database-preparation.zh-cn.md @@ -17,13 +17,13 @@ menu: # 数据库准备 -在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>=10)、MySQL(>=5.7)、SQLite 和 MSSQL(>=2008R2 SP3)这几种数据库。本页将指导您准备数据库。由于 PostgreSQL 和 MySQL 在生产环境中被广泛使用,因此本文档将仅涵盖这两种数据库。如果您计划使用 SQLite,则可以忽略本章内容。 +在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>= 12)、MySQL(>= 8.0)、SQLite 和 MSSQL(>= 2012 SP4)这几种数据库。本页将指导您准备数据库。由于 PostgreSQL 和 MySQL 在生产环境中被广泛使用,因此本文档将仅涵盖这两种数据库。如果您计划使用 SQLite,则可以忽略本章内容。 数据库实例可以与 Gitea 实例在相同机器上(本地数据库),也可以与 Gitea 实例在不同机器上(远程数据库)。 注意:以下所有步骤要求您的选择的数据库引擎已安装在您的系统上。对于远程数据库设置,请在数据库实例上安装服务器应用程序,在 Gitea 服务器上安装客户端程序。客户端程序用于测试 Gitea 服务器与数据库之间的连接,而 Gitea 本身使用 Go 提供的数据库驱动程序完成相同的任务。此外,请确保服务器和客户端使用相同的引擎版本,以使某些引擎功能正常工作。出于安全原因,请使用安全密码保护 `root`(MySQL)或 `postgres`(PostgreSQL)数据库超级用户。以下步骤假设您在数据库和 Gitea 服务器上都使用 Linux。 -## MySQL +## MySQL/MariaDB 1. 对于远程数据库设置,您需要让 MySQL 监听您的 IP 地址。编辑数据库实例上的 `/etc/mysql/my.cnf` 文件中的 `bind-address` 选项为: @@ -182,7 +182,7 @@ menu: - 在数据库服务器证书中,`Subject Alternative Name` 或 `Common Name` 条目之一必须是数据库实例的完全限定域名(FQDN)(例如 `db.example.com`)。在数据库客户端证书中,上述提到的条目之一必须包含 Gitea 将用于连接的数据库用户名。 - 您需要将 Gitea 和数据库服务器的域名映射到它们各自的 IP 地址。可以为它们设置 DNS 记录,也可以在每个系统上的 `/etc/hosts`(Windows 中的 `%WINDIR%\System32\drivers\etc\hosts`)中添加本地映射。这样可以通过域名而不是 IP 地址进行数据库连接。有关详细信息,请参阅您系统的文档。 -### PostgreSQL +### PostgreSQL TLS Gitea 使用的 PostgreSQL 驱动程序支持双向 TLS。在双向 TLS 中,数据库客户端和服务器通过将各自的证书发送给对方进行验证来相互认证。换句话说,服务器验证客户端证书,客户端验证服务器证书。 @@ -250,7 +250,7 @@ Gitea 使用的 PostgreSQL 驱动程序支持双向 TLS。在双向 TLS 中, 您将被提示输入数据库用户的密码,然后连接到数据库。 -### MySQL +### MySQL/MariaDB TLS 虽然 Gitea 使用的MySQL驱动程序也支持双向 TLS,但目前 Gitea 仅支持单向 TLS。有关详细信息,请参见工单#10828。 diff --git a/docs/content/installation/from-binary.fr-fr.md b/docs/content/installation/from-binary.fr-fr.md deleted file mode 100644 index ac534263e1..0000000000 --- a/docs/content/installation/from-binary.fr-fr.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation avec le binaire pré-compilé" -slug: "install-from-binary" -sidebar_position: 15 -toc: false -draft: false -aliases: - - /fr-fr/install-from-binary -menu: - sidebar: - parent: "installation" - name: "Binaire pré-compilé" - sidebar_position: 15 - identifier: "install-from-binary" ---- - -# Installation avec le binaire pré-compilé - -Tous les binaires sont livrés avec le support de SQLite, MySQL et PostgreSQL, et sont construits avec les ressources incorporées. Gardez à l'esprit que cela peut être différent pour les versions antérieures. L'installation basée sur nos binaires est assez simple, il suffit de choisir le fichier correspondant à votre plateforme à partir de la [page de téléchargement](https://dl.gitea.com/gitea). Copiez l'URL et remplacer l'URL dans les commandes suivantes par la nouvelle: - -``` -wget -O gitea https://dl.gitea.com/gitea/@version@/gitea-@version@-linux-amd64 -chmod +x gitea -``` - -## Test - -Après avoir suivi les étapes ci-dessus, vous aurez un binaire `gitea` dans votre répertoire de travail. En premier lieu, vous pouvez tester si le binaire fonctionne comme prévu et ensuite vous pouvez le copier vers la destination où vous souhaitez le stocker. Lorsque vous lancez Gitea manuellement à partir de votre CLI, vous pouvez toujours le tuer en appuyant sur `Ctrl + C`. - -``` -./gitea web -``` - -## Dépannage - -### Anciennes version de glibc - -Les anciennes distributions Linux (comme Debian 7 ou CentOS 6) peuvent ne pas être capable d'exécuter le binaire Gitea, résultant généralement une erreur du type ```./gitea: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found (required by ./gitea)```. Cette erreur est due au driver SQLite que nous intégrons dans le binaire Gitea. Dans le futur, nous fournirons des binaires sans la dépendance pour la bibliothèque glibc. En attendant, vous pouvez mettre à jour votre distribution ou installer Gitea depuis le [code source](installation/from-source.md). - -### Exécuter Gitea avec un autre port - -Si vous obtenez l'erreur `702 runWeb()] [E] Failed to start server: listen tcp 0.0.0.0:3000: bind: address already in use`, Gitea à besoin d'utiliser un autre port. Vous pouvez changer le port par défaut en utilisant `./gitea web -p $PORT`. - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/from-binary.zh-tw.md b/docs/content/installation/from-binary.zh-tw.md deleted file mode 100644 index 1fff90aa37..0000000000 --- a/docs/content/installation/from-binary.zh-tw.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "執行檔安裝" -slug: "install-from-binary" -sidebar_position: 15 -toc: false -draft: false -aliases: - - /zh-tw/install-from-binary -menu: - sidebar: - parent: "installation" - name: "執行檔" - sidebar_position: 15 - identifier: "install-from-binary" ---- - -# 從執行檔安裝 - -所有的執行檔皆支援 SQLite, MySQL and PostgreSQL,且所有檔案都已經包在執行檔內,這一點跟之前的版本有所不同。關於執行檔的安裝方式非常簡單,只要從[下載頁面](https://dl.gitea.com/gitea)選擇相對應平台,複製下載連結,使用底下指令就可以完成了: - -``` -wget -O gitea https://dl.gitea.com/gitea/@version@/gitea-@version@-linux-amd64 -chmod +x gitea -``` - -## 測試 - -執行完上述步驟,您將會得到 `gita` 執行檔,在複製到遠端伺服器前,您可以先測試看看,在命令列執行完成後,可以透過 `Ctrl + C` 關閉程式。 - -``` -./gitea web -``` - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/installation/from-package.en-us.md b/docs/content/installation/from-package.en-us.md index 4c29c31efc..aceb2029d2 100644 --- a/docs/content/installation/from-package.en-us.md +++ b/docs/content/installation/from-package.en-us.md @@ -15,22 +15,23 @@ menu: identifier: "install-from-package" --- -# Official packages +# Installation from Package -## macOS +## Official packages + +### macOS Currently, the only supported method of installation on MacOS is [Homebrew](http://brew.sh/). Following the [deployment from binary](installation/from-binary.md) guide may work, but is not supported. To install Gitea via `brew`: ``` -brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea brew install gitea ``` -# Unofficial packages +## Unofficial packages -## Alpine Linux +### Alpine Linux Alpine Linux has [Gitea](https://pkgs.alpinelinux.org/packages?name=gitea&branch=edge) in its community repository which follows the latest stable version. @@ -38,7 +39,7 @@ Alpine Linux has [Gitea](https://pkgs.alpinelinux.org/packages?name=gitea&branch apk add gitea ``` -## Arch Linux +### Arch Linux The rolling release distribution has [Gitea](https://www.archlinux.org/packages/extra/x86_64/gitea/) in their official extra repository and package updates are provided with new Gitea releases. @@ -46,7 +47,7 @@ The rolling release distribution has [Gitea](https://www.archlinux.org/packages/ pacman -S gitea ``` -## Arch Linux ARM +### Arch Linux ARM Arch Linux ARM provides packages for [aarch64](https://archlinuxarm.org/packages/aarch64/gitea), [armv7h](https://archlinuxarm.org/packages/armv7h/gitea) and [armv6h](https://archlinuxarm.org/packages/armv6h/gitea). @@ -54,7 +55,7 @@ Arch Linux ARM provides packages for [aarch64](https://archlinuxarm.org/packages pacman -S gitea ``` -## Gentoo Linux +### Gentoo Linux The rolling release distribution has [Gitea](https://packages.gentoo.org/packages/www-apps/gitea) in their official community repository and package updates are provided with new Gitea releases. @@ -62,20 +63,21 @@ The rolling release distribution has [Gitea](https://packages.gentoo.org/package emerge gitea -va ``` -## Canonical Snap +### Canonical Snap There is a [Gitea Snap](https://snapcraft.io/gitea) package which follows the latest stable version. +*Note: The Gitea snap package is [strictly confined](https://snapcraft.io/docs/snap-confinement). Strictly confined snaps run in complete isolation, so some of the Gitea functionals may not work with the confinement* ```sh snap install gitea ``` -## SUSE and openSUSE +### SUSE and openSUSE OpenSUSE build service provides packages for [openSUSE and SLE](https://software.opensuse.org/download/package?package=gitea&project=devel%3Atools%3Ascm) in the Development Software Configuration Management Repository -## Windows +### Windows There is a [Gitea](https://chocolatey.org/packages/gitea) package for Windows by [Chocolatey](https://chocolatey.org/). @@ -85,7 +87,7 @@ choco install gitea Or follow the [deployment from binary](installation/from-binary.md) guide. -## FreeBSD +### FreeBSD A FreeBSD port `www/gitea` is available. To install the pre-built binary package: @@ -108,7 +110,7 @@ is in `/usr/local/etc/rc.d/gitea`. To enable Gitea to run as a service, run `sysrc gitea_enable=YES` and start it with `service gitea start`. -## Others +### Others Various other third-party packages of Gitea exist. To see a curated list, head over to [awesome-gitea](https://gitea.com/gitea/awesome-gitea/src/branch/master/README.md#user-content-packages). diff --git a/docs/content/installation/from-package.fr-fr.md b/docs/content/installation/from-package.fr-fr.md deleted file mode 100644 index 256c913dcf..0000000000 --- a/docs/content/installation/from-package.fr-fr.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "Installation depuis le gestionnaire de paquets" -slug: "install-from-package" -sidebar_position: 20 -toc: false -draft: false -aliases: - - /fr-fr/install-from-package -menu: - sidebar: - parent: "installation" - name: "Gestionnaire de paquets" - sidebar_position: 20 - identifier: "install-from-package" ---- - -# Installation depuis le gestionnaire de paquets - -## Linux - -Nous n'avons pas encore publié de paquet pour Linux, nous allons mettre à jour cette page directement lorsque nous commencerons à publier des paquets pour toutes distributions Linux. En attendant, vous devriez suivre les [instructions d'installation](installation/from-binary.md) avec le binaire pré-compilé. - -## Windows - -Nous n'avons pas encore publié de paquet pour Windows, nous allons mettre à jour cette page directement lorsque nous commencerons à publier des paquets sous la forme de fichiers `MSI` ou via [Chocolatey](https://chocolatey.org/). En attendant, vous devriez suivre les [instructions d'installation](installation/from-binary.md) avec le binaire pré-compilé. - -## macOS - -Actuellement, nous ne supportons que l'installation via `brew` pour macOS. Si vous n'utilisez pas [Homebrew](http://brew.sh/), vous pouvez suivre les [instructions d'installation](installation/from-binary.md) avec le binaire pré-compilé. Pour installer Gitea depuis `brew`, utilisez les commandes suivantes : - -``` -brew tap go-gitea/gitea -brew install gitea -``` - -## FreeBSD - -Le portage FreeBSD `www/gitea` est disponible. Vous pouvez également installer le paquet pré-compilé avec la commande suivante: - -``` -pkg install gitea -``` - -Pour une version plus récente, ou pour les instructions de compilations, veuillez consulter la documentation officielle de FreeBSD : [install it from the port](https://www.freebsd.org/doc/handbook/ports-using.html) - -``` -su - -cd /usr/ports/www/gitea -make install clean -``` - -Le port utilise la schéma standard du système de fichiers FreeBSD : Les fichiers de configuration sont localisés dans le répertoire `/usr/local/etc/gitea`, les modèles, options, plugins et thèmes sont localisés dans le répertoire `/usr/local/share/gitea`, et le script de démarrage se situe dans `/usr/local/etc/rc.d/gitea`. - -Pour exécuter Gitea en tant que service, utilisez la commande `sysrc gitea_enable=YES` et la commande `service gitea start` pour démarrer le service. - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/from-package.zh-cn.md b/docs/content/installation/from-package.zh-cn.md index 66db4d322d..929782f23c 100644 --- a/docs/content/installation/from-package.zh-cn.md +++ b/docs/content/installation/from-package.zh-cn.md @@ -22,7 +22,6 @@ menu: macOS 平台下当前我们仅支持通过 `brew` 来安装。如果你没有安装 [Homebrew](http://brew.sh/),你也可以查看 [从二进制安装](installation/from-binary.md)。在你安装了 `brew` 之后, 你可以执行以下命令: ``` -brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea brew install gitea ``` diff --git a/docs/content/installation/from-package.zh-tw.md b/docs/content/installation/from-package.zh-tw.md deleted file mode 100644 index 5a417ee202..0000000000 --- a/docs/content/installation/from-package.zh-tw.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "套件安裝" -slug: "install-from-package" -sidebar_position: 20 -toc: false -draft: false -aliases: - - /zh-tw/install-from-package -menu: - sidebar: - parent: "installation" - name: "套件安裝" - sidebar_position: 20 - identifier: "install-from-package" ---- - -# 從套件安裝 - -## Linux - -目前尚未發佈任何 Linux 套件,如果我們發佈了,會直接更新此網頁。在這之前請先參考[執行檔安裝](installation/from-binary.md)方式。 - -## Windows - -在 Windows 作業系統你可以透過 [Chocolatey](https://chocolatey.org/) 套件管理器安裝 [Gitea](https://chocolatey.org/packages/gitea) 套件: - -```sh -choco install gitea -``` - -也可以參考[執行檔安裝](installation/from-binary.md)方式。 - -## macOS - -目前我們只支援透過 `brew` 來安裝套件。假如您尚未使用 [Homebrew](http://brew.sh/),您就必須參考[執行檔安裝](installation/from-binary.md)方式。透過 `brew` 安裝 Gitea,您只需要執行底下指令: - -``` -brew tap go-gitea/gitea -brew install gitea -``` - -## FreeBSD - -下載 FreeBSD port `www/gitea` 套件。你可以安裝 pre-built 執行檔: - -``` -pkg install gitea -``` - -對於最新版本或想要自行編譯特定選項,請使用 [port 安裝](https://www.freebsd.org/doc/handbook/ports-using.html): - -``` -su - -cd /usr/ports/www/gitea -make install clean -``` - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/installation/from-source.en-us.md b/docs/content/installation/from-source.en-us.md index ef720f0824..f6a95e8d0c 100644 --- a/docs/content/installation/from-source.en-us.md +++ b/docs/content/installation/from-source.en-us.md @@ -128,8 +128,6 @@ If pre-built frontend files are present it is possible to only build the backend TAGS="bindata" make backend ``` -Webpack source maps are by default enabled in development builds and disabled in production builds. They can be enabled by setting the `ENABLE_SOURCEMAP=true` environment variable. - ## Test After following the steps above, a `gitea` binary will be available in the working directory. @@ -260,3 +258,11 @@ GOARCH=amd64 \ TAGS="bindata sqlite sqlite_unlock_notify" \ make build ``` + +## Source Maps + +By default, gitea generates reduced source maps for frontend files to conserve space. This can be controlled with the `ENABLE_SOURCEMAP` environment variable: + +- `ENABLE_SOURCEMAP=true` generates all source maps, the default for development builds +- `ENABLE_SOURCEMAP=reduced` generates limited source maps, the default for production builds +- `ENABLE_SOURCEMAP=false` generates no source maps diff --git a/docs/content/installation/from-source.fr-fr.md b/docs/content/installation/from-source.fr-fr.md deleted file mode 100644 index fe0f173bda..0000000000 --- a/docs/content/installation/from-source.fr-fr.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation depuis le code source" -slug: "install-from-source" -sidebar_position: 30 -toc: false -draft: false -aliases: - - /fr-fr/install-from-source -menu: - sidebar: - parent: "installation" - name: "Code source" - sidebar_position: 30 - identifier: "install-from-source" ---- - -# Installation depuis le code source - -Nous ne couvrirons pas les bases de la configuration de Golang dans ce guide. Si vous ne savez pas comment démarrer un environnement fonctionnel, vous devrez suivre les [instructions d'installation](https://golang.org/doc/install) officielles. - -**Attention**: La version 1.7 ou suppérieur de Go est nécessaire - -## Téléchargement - -Tout d'abord, vous devez récupérer le code source, la manière la plus simple est d'utiliser directement Go. Il suffit d'appeler les commandes suivantes pour récupérer le code source et passer au répertoire de travail. - -``` -go get -d -u code.gitea.io/gitea -cd $GOPATH/src/code.gitea.io/gitea -``` - -Maintenant, il est temps de décider quelle version de Gitea vous souhaitez compiler et installer. Actuellement, ils existent plusieurs options possibles. Si vous voulez compiler notre branche `master`, vous pouvez directement passer à la [section compilation](#compilation), cette branche représente la dernière version en cours de développement et n'a pas vocation à être utiliser en production. - -Si vous souhaitez compiler la dernière version stable, utilisez les étiquettes ou les différentes branches disponibles. Vous pouvez voir les branches disponibles et comment utiliser cette branche avec ces commandes: - -``` -git branch -a -git checkout v@version@ -``` - -Si vous souhaitez valider une demande d'ajout (_Pull request_), vous devez activer cette branche en premier : - -``` -git fetch origin pull/xyz/head:pr-xyz # xyz is PR value -``` - -Enfin, vous pouvez directement utiliser les versions étiquettées (ex : `v@version@`). Pour utiliser les étiquettes, vous devez lister les étiquettes disponibles et choisir une étiquette spécifique avec les commandes suivantes : - -``` -git tag -l -git checkout v@version@ -git checkout pr-xyz -``` - -## Compilation - -Comme nous regroupons déjà toutes les bibliothèques requises pour compiler Gitea, vous pouvez continuer avec le processus de compilation lui-même. Nous fournissons diverses [tâches Make](https://github.com/go-gitea/gitea/blob/main/Makefile) pour rendre le processus de construction aussi simple que possible. [Voyez ici comment obtenir Make](/fr-fr/hacking-on-gitea/). Selon vos besoins, vous pourrez éventuellement ajouter diverses options de compilation, vous pouvez choisir entre ces options : - -* `bindata`: Intègre toutes les ressources nécessaires à l'exécution d'une instance de Gitea, ce qui rend un déploiement facile car il n'est pas nécessaire de se préoccuper des fichiers supplémentaires. -* `sqlite sqlite_unlock_notify`: Active la prise en charge d'une base de données [SQLite3](https://sqlite.org/), ceci n'est recommandé que pour les petites installations de Gitea. -* `pam`: Active la prise en charge de PAM (mLinux Pluggable Authentication Modules), très utile si vos utilisateurs doivent être authentifiés avec les comptes du système. - -Il est temps de compiler le binaire, nous suggérons d'intégrer les ressources avec l'option de compilation `bindata`: - -``` -TAGS="bindata" make build -``` - -## Test - -Après avoir suivi toutes les étapes, vous devriez avoir le binaire `gitea` dans votre répertoire courant. Dans un premier temps, vous pouvez tester qu'il fonctionne puis, dans un second temps, vous pouvez le copier dans la destination de votre choix. Lorsque vous lancez Gitea manuellement à partir de votre CLI, vous pouvez toujours le tuer en appuyant sur `Ctrl + C`. - -``` -./gitea web -``` - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/from-source.zh-cn.md b/docs/content/installation/from-source.zh-cn.md index 260ccbee3c..40a314340c 100644 --- a/docs/content/installation/from-source.zh-cn.md +++ b/docs/content/installation/from-source.zh-cn.md @@ -100,8 +100,6 @@ TAGS="bindata sqlite sqlite_unlock_notify" make build TAGS="bindata" make backend ``` -在开发构建中,默认启用 Webpack 源映射,在生产构建中禁用。可以通过设置`ENABLE_SOURCEMAP=true`环境变量来启用它们。 - ## 测试 按照上述步骤完成后,工作目录中将会有一个`gitea`二进制文件。可以从该目录进行测试,或将其移动到带有测试数据的目录中。当手动从命令行启动 Gitea 时,可以通过按下`Ctrl + C`来停止程序。 @@ -221,3 +219,11 @@ GOARCH=amd64 \ TAGS="bindata sqlite sqlite_unlock_notify" \ make build ``` + +## Source Map + +默认情况下,gitea 会为前端文件生成精简的 Source Map 以节省空间。 这可以通过“ENABLE_SOURCEMAP”环境变量进行控制: + +- `ENABLE_SOURCEMAP=true` 生成所有Source Map,这是开发版本的默认设置 +- `ENABLE_SOURCEMAP=reduced` 生成有限的Source Map,这是生产版本的默认设置 +- `ENABLE_SOURCEMAP=false` 不生成Source Map diff --git a/docs/content/installation/from-source.zh-tw.md b/docs/content/installation/from-source.zh-tw.md deleted file mode 100644 index eedfc36f0c..0000000000 --- a/docs/content/installation/from-source.zh-tw.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "原始碼安裝" -slug: "install-from-source" -sidebar_position: 30 -toc: false -draft: false -aliases: - - /zh-tw/install-from-source -menu: - sidebar: - parent: "installation" - name: "原始碼安裝" - sidebar_position: 30 - identifier: "install-from-source" ---- - -# 從原始碼安裝 - -我們不會在本文教大家如何安裝 Golang 環境。假如您不知道如何設定環境,請直接參考[官方安裝文件](https://golang.org/doc/install)。 - -## 下載 - -首先您必須先下載原始碼,最簡單的方式就是透過 Go 指令下載,請透過底下指令下載原始碼並且切換到工作目錄。 - -``` -go get -d -u code.gitea.io/gitea -cd $GOPATH/src/code.gitea.io/gitea -``` - -現在該決定您要編譯或安裝的 Gitea 版本,您有很多可以選擇。如果您想編譯 `master` 版本,你可以直接跳到[編譯章節](#編譯),這是我們開發分支,雖然很穩定,但是不建議用在正式環境。 - -假如您想要編譯最新穩定版本,可以執行底下命令切換到正確版本: - -``` -git branch -a -git checkout v@version@ -``` - -最後您也可以直接編譯最新的標籤版本像是 `v@version@`,假如您想要從原始碼編譯,這方法是最合適的,在編譯標籤版本前,您需要列出當下所有標籤,並且直接切換到標籤版本,請使用底下指令:: - -``` -git tag -l -git checkout v@version@ -``` - -## 編譯 - -完成設定相依性套件環境等工作後,您就可以開始編譯工作了。我們提供了不同的[編譯選項](https://github.com/go-gitea/gitea/blob/main/Makefile) ,讓編譯過程更加簡單。您可以根據需求來調整編譯選項,底下是可用的編譯選項說明: - -* `bindata`: 使用此標籤來嵌入所有 Gitea 相關資源,您不用擔心其他額外檔案,對於部署來說非常方便。 -* `sqlite sqlite_unlock_notify`: 使用此標籤來啟用 [SQLite3](https://sqlite.org/) 資料庫,建議只有少數人時才使用此模式。 -* `pam`: 使用此標籤來啟用 PAM (Linux Pluggable Authentication Modules) 認證,對於系統使用者來說,此方式最方便了。 - -現在您可以開始編譯執行檔了,我們建議使用 `bindata` 編譯選項: - -``` -TAGS="bindata" make build -``` - -**注意**: 因為使用了套件管理工具,我們建議 Go 環境版本為 1.6 或者是更高,這樣不用在 Go 1.5 版本設定全域變數 `GO15VENDOREXPERIMENT`。 - -## 測試 - -完成上述步驟後,您可以在當下目錄發現 `gitea` 執行檔,在複製執行檔到遠端環境之前,您必須透過底下指令執行測試,使用 `Ctrl + C` 則可以關閉當下 gitea 程序。 - -``` -./gitea web -``` - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/installation/on-kubernetes.zh-tw.md b/docs/content/installation/on-kubernetes.zh-tw.md deleted file mode 100644 index 294e11ee1e..0000000000 --- a/docs/content/installation/on-kubernetes.zh-tw.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -date: "2020-03-19T19:27:00+02:00" -title: "在 Kubernetes 安裝" -slug: "install-on-kubernetes" -sidebar_position: 80 -toc: false -draft: false -aliases: - - /zh-tw/install-on-kubernetes -menu: - sidebar: - parent: "installation" - name: "Kubernetes" - sidebar_position: 80 - identifier: "install-on-kubernetes" ---- - -# 使用 Helm 安裝 (在 Kubernetes) - -Gitea 提供 Helm Chart 用來安裝於 kubernetes。 - -非自訂安裝可使用下列指令: - -``` -helm repo add gitea-charts https://dl.gitea.com/charts/ -helm install gitea gitea-charts/gitea -``` - -若您想自訂安裝(包括使用 kubernetes ingress),請前往完整的 [Gitea helm chart configuration details](https://gitea.com/gitea/helm-chart/) - -## 運行狀況檢查終端節點 - -Gitea 附帶了一個運行狀況檢查端點 `/api/healthz`,你可以像這樣在 kubernetes 中配置它: - -```yaml - livenessProbe: - httpGet: - path: /api/healthz - port: http - initialDelaySeconds: 200 - timeoutSeconds: 5 - periodSeconds: 10 - successThreshold: 1 - failureThreshold: 10 -``` - -成功的運行狀況檢查回應將使用 HTTP 代碼 `200` 進行回應,下面是示例: - -``` -HTTP/1.1 200 OK - -{ - "status": "pass", - "description": "Gitea: Git with a cup of tea", - "checks": { - "cache:ping": [ - { - "status": "pass", - "time": "2022-02-19T09:16:08Z" - } - ], - "database:ping": [ - { - "status": "pass", - "time": "2022-02-19T09:16:08Z" - } - ] - } -} -``` - -有關更多信息,請參考kubernetes文檔[定義一個存活態 HTTP請求接口](https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) diff --git a/docs/content/installation/run-as-service-in-ubuntu.en-us.md b/docs/content/installation/run-as-service-in-ubuntu.en-us.md index ab862b373f..4e169d6bcc 100644 --- a/docs/content/installation/run-as-service-in-ubuntu.en-us.md +++ b/docs/content/installation/run-as-service-in-ubuntu.en-us.md @@ -1,6 +1,6 @@ --- date: "2017-07-21T12:00:00+02:00" -title: "Run as service in Linux" +title: "Run as a Linux service" slug: "linux-service" sidebar_position: 40 toc: false @@ -15,11 +15,11 @@ menu: identifier: "linux-service" --- -### Run Gitea as Linux service +# Run as a Linux service -You can run Gitea as service, using either systemd or supervisor. The steps below tested on Ubuntu 16.04, but those should work on any Linux distributions (with little modification). +You can run Gitea as a Linux service, using either systemd or supervisor. The steps below tested on Ubuntu 16.04, but those should work on any Linux distributions (with little modification). -#### Using systemd +## Using systemd Copy the sample [gitea.service](https://github.com/go-gitea/gitea/blob/main/contrib/systemd/gitea.service) to `/etc/systemd/system/gitea.service`, then edit the file with your favorite editor. @@ -41,7 +41,7 @@ If you have systemd version 220 or later, you can enable and immediately start G sudo systemctl enable gitea --now ``` -#### Using supervisor +## Using supervisor Install supervisor by running below command in terminal: diff --git a/docs/content/installation/run-as-service-in-ubuntu.zh-tw.md b/docs/content/installation/run-as-service-in-ubuntu.zh-tw.md deleted file mode 100644 index 51b23deaa4..0000000000 --- a/docs/content/installation/run-as-service-in-ubuntu.zh-tw.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -date: "2017-07-21T12:00:00+02:00" -title: "在 Linux 中以服務執行" -slug: "linux-service" -sidebar_position: 40 -toc: false -draft: false -aliases: - - /zh-tw/linux-service -menu: - sidebar: - parent: "installation" - name: "Linux 服務" - sidebar_position: 40 - identifier: "linux-service" ---- - -### 以 Linux 服務執行 Gitea - -您可使用 systemd 或 supervisor 以服務的方式執行 Gitea。下列步驟已在 Ubuntu 16.04 中測試,但它們應該適用於所有的 Linux 發行版(只需要一些小小的調整)。 - -#### 使用 systemd - -複製範例 [gitea.service](https://github.com/go-gitea/gitea/blob/main/contrib/systemd/gitea.service) 到 `/etc/systemd/system/gitea.service` 後用您喜愛的文字編輯器開啟檔案。 - -取消註解任何需要在此系統上啟動的服務像是 MySQL。 - -修改 user, home directory 和其它必要的啟動參數。若預設埠已被占用請修改埠號或移除「-p」旗標。 - -在系統啟動時啟用並執行 Gitea: - -``` -sudo systemctl enable gitea -sudo systemctl start gitea -``` - -若您使用 systemd 220 或更新版本,您能以一行指令啟動並立即執行 Gitea: - -``` -sudo systemctl enable gitea --now -``` - -#### 使用 supervisor - -在終端機使用下列指令安裝 supervisor: - -``` -sudo apt install supervisor -``` - -為 supervisor 建立 log 資料夾: - -``` -# assuming Gitea is installed in /home/git/gitea/ -mkdir /home/git/gitea/log/supervisor -``` - -附加範例 [supervisord config](https://github.com/go-gitea/gitea/blob/main/contrib/supervisor/gitea) 的設定值到 `/etc/supervisor/supervisord.conf`。 - -用您喜愛的文字編輯器修改使用者(git)和家目錄(/home/git)設定以符合部署環境。若預設埠已被占用請修改埠號或移除「-p」旗標。 - -最後設定在系統啟動時啟用並執行 supervisor: - -``` -sudo systemctl enable supervisor -sudo systemctl start supervisor -``` - -若您使用 systemd 220 或更新版本,您能以一行指令啟動並立即執行 supervisor: - -``` -sudo systemctl enable supervisor --now -``` diff --git a/docs/content/installation/upgrade-from-gitea.en-us.md b/docs/content/installation/upgrade-from-gitea.en-us.md index 984f2d1336..4a5f21778a 100644 --- a/docs/content/installation/upgrade-from-gitea.en-us.md +++ b/docs/content/installation/upgrade-from-gitea.en-us.md @@ -65,7 +65,7 @@ If you are using cloud services or filesystems with snapshot feature, a snapshot for the Gitea data volume and related object storage is more convenient. After all of steps have been prepared, download the new version, stop the application, perform a backup and -then start the new application. On each startup, Gitea verifies that the database is up to date and will automtically +then start the new application. On each startup, Gitea verifies that the database is up to date and will automatically perform any necessary migrations. Depending on the size of the database, this can take some additional time on the first launch during which the application will be unavailable. diff --git a/docs/content/installation/upgrade-from-gogs.en-us.md b/docs/content/installation/upgrade-from-gogs.en-us.md deleted file mode 100644 index 47cddc1c30..0000000000 --- a/docs/content/installation/upgrade-from-gogs.en-us.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "Upgrade from Gogs" -slug: "upgrade-from-gogs" -sidebar_position: 101 -toc: false -draft: false -aliases: - - /en-us/upgrade-from-gogs -menu: - sidebar: - parent: "installation" - name: "Upgrade From Gogs" - sidebar_position: 101 - identifier: "upgrade-from-gogs" ---- - -# Upgrade from Gogs - -Gogs, version 0.9.146 and older, can be easily migrated to Gitea. - -There are some basic steps to follow. On a Linux system run as the Gogs user: - -* Create a Gogs backup with `gogs backup`. This creates `gogs-backup-[timestamp].zip` file - containing all important Gogs data. You would need it if you wanted to move to the `gogs` back later. -* Download the file matching the destination platform from the [downloads page](https://dl.gitea.com/gitea/). - It should be `1.0.x` version. Migrating from `gogs` to any other version is impossible. -* Put the binary at the desired install location. -* Copy `gogs/custom/conf/app.ini` to `gitea/custom/conf/app.ini`. -* Copy custom `templates, public` from `gogs/custom/` to `gitea/custom/`. -* For any other custom folders, such as `gitignore, label, license, locale, readme` in - `gogs/custom/conf`, copy them to `gitea/custom/options`. -* Copy `gogs/data/` to `gitea/data/`. It contains issue attachments and avatars. -* Verify by starting Gitea with `gitea web`. -* Enter Gitea admin panel on the UI, run `Rewrite '.ssh/authorized_keys' file`. -* Launch every major version of the binary ( `1.1.4` → `1.2.3` → `1.3.4` → `1.4.2` → etc ) to migrate database. -* If custom or config path was changed, run `Rewrite all update hook of repositories`. - -## Change gogs specific information - -* Rename `gogs-repositories/` to `gitea-repositories/` -* Rename `gogs-data/` to `gitea-data/` -* In `gitea/custom/conf/app.ini` change: - - FROM: - - ```ini - [database] - PATH = /home/:USER/gogs/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gogs-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gogs-data/avatars - [log] - ROOT_PATH = /home/:USER/gogs/log - ``` - - TO: - - ```ini - [database] - PATH = /home/:USER/gitea/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gitea-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gitea-data/avatars - [log] - ROOT_PATH = /home/:USER/gitea/log - ``` - -* Verify by starting Gitea with `gitea web` - -## Upgrading to most recent `gitea` version - -After successful migration from `gogs` to `gitea 1.0.x`, it is possible to upgrade `gitea` to a modern version -in a two steps process. - -Upgrade to [`gitea 1.6.4`](https://dl.gitea.com/gitea/1.6.4/) first. Download the file matching -the destination platform from the [downloads page](https://dl.gitea.com/gitea/1.6.4/) and replace the binary. -Run Gitea at least once and check that everything works as expected. - -Then repeat the procedure, but this time using the [latest release](https://dl.gitea.com/gitea/@version@/). - -## Upgrading from a more recent version of Gogs - -Upgrading from a more recent version of Gogs (up to `0.11.x`) may also be possible, but will require a bit more work. -See [#4286](https://github.com/go-gitea/gitea/issues/4286), which includes various Gogs `0.11.x` versions. - -Upgrading from Gogs `0.12.x` and above will be increasingly more difficult as the projects diverge further apart in configuration and schema. - -## Troubleshooting - -* If errors are encountered relating to custom templates in the `gitea/custom/templates` - folder, try moving the templates causing the errors away one by one. They may not be - compatible with Gitea or an update. - -## Add Gitea to startup on Unix - -Update the appropriate file from [gitea/contrib](https://github.com/go-gitea/gitea/tree/main/contrib) -with the right environment variables. - -For distros with systemd: - -* Copy the updated script to `/etc/systemd/system/gitea.service` -* Add the service to the startup with: `sudo systemctl enable gitea` -* Disable old gogs startup script: `sudo systemctl disable gogs` - -For distros with SysVinit: - -* Copy the updated script to `/etc/init.d/gitea` -* Add the service to the startup with: `sudo rc-update add gitea` -* Disable old gogs startup script: `sudo rc-update del gogs` diff --git a/docs/content/installation/upgrade-from-gogs.fr-fr.md b/docs/content/installation/upgrade-from-gogs.fr-fr.md deleted file mode 100644 index a5cd875982..0000000000 --- a/docs/content/installation/upgrade-from-gogs.fr-fr.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Mise à jour depuis Gogs" -slug: "upgrade-from-gogs" -sidebar_position: 101 -toc: false -draft: false -aliases: - - /fr-fr/upgrade-from-gogs -menu: - sidebar: - parent: "installation" - name: "Depuis Gogs" - sidebar_position: 101 - identifier: "upgrade-from-gogs" ---- - -# Mise à jour depuis Gogs - -À partir de la version 0.9.146 (schéma de la base de données : version 15) de Gogs, Il est possible de migrer vers Gitea simplement et sans encombre. - -Veuillez suivre les étapes ci-dessous. Sur Unix, toute les commandes s'exécutent en tant que l'utilisateur utilisé pour votre installation de Gogs : - -* Crééer une sauvegarde de Gogs avec la commande `gogs dump`. Le fichier nouvellement créé `gogs-dump-[timestamp].zip` contient toutes les données de votre instance de Gogs. -* Téléchargez le fichier correspondant à votre plateforme à partir de la [page de téléchargements](https://dl.gitea.com/gitea). -* Mettez la binaire dans le répertoire d'installation souhaité. -* Copiez le fichier `gogs/custom/conf/app.ini` vers `gitea/custom/conf/app.ini`. -* Si vous avez personnalisé les répertoires `templates, public` dans `gogs/custom/`, copiez-les vers `gitea/custom/`. -* Si vous avez d'autres répertoires personnalisés comme `gitignore, label, license, locale, readme` dans `gogs/custom/conf` copiez-les vers `gitea/custom/options`. -* Copiez le répertoire `gogs/data/` vers `gitea/data/`. -* Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`. -* Lancez le binaire de version majeure en version majeure ( `1.1.4` → `1.2.3` → `1.3.4` → `1.4.2` → etc ) afin de récupérer les migrations de base de données. -* Connectez vous au panel d'administration de Gitea et exécutez l'action `Rewrite '.ssh/authorized_keys' file`, puis l'action `Rewrite all update hook of repositories` (obligatoire si le chemin menant à votre configuration personnalisée à changé). - -## Modifier les informations spécifiques de gogs - -* Renommez `gogs-repositories/` vers `gitea-repositories/` -* Renommez `gogs-data/` to `gitea-data/` -* Dans votre fichier `gitea/custom/conf/app.ini`, modifiez les éléments suivants: - - DE : - - ```ini - [database] - PATH = /home/:USER/gogs/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gogs-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gogs-data/avatars - [log] - ROOT_PATH = /home/:USER/gogs/log - ``` - - VERS : - - ```ini - [database] - PATH = /home/:USER/gitea/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gitea-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gitea-data/avatars - [log] - ROOT_PATH = /home/:USER/gitea/log - ``` - -* Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`. - -## Dépannage - -* Si vous rencontrez des erreurs relatives à des modèles personnalisés dans le dossier `gitea/custom/templates`, essayez de déplacer un par un les modèles provoquant les erreurs. Il est possible qu'ils ne soient pas compatibles avec Gitea. - -## Démarrer automatiquement Gitea (Unix) - -Distributions utilisant systemd: - -* Copiez le script mis à jour vers `/etc/systemd/system/gitea.service` -* Ajoutez le service avec la commande `sudo systemctl enable gitea` -* Désactivez Gogs avec la commande `sudo systemctl disable gogs` - -Distributions utilisant SysVinit: - -* Copiez le script mis à jour vers `/etc/init.d/gitea` -* Ajoutez le service avec la commande `sudo rc-update add gitea` -* Désactivez Gogs avec la commande `sudo rc-update del gogs` diff --git a/docs/content/installation/upgrade-from-gogs.zh-cn.md b/docs/content/installation/upgrade-from-gogs.zh-cn.md deleted file mode 100644 index 29788c929f..0000000000 --- a/docs/content/installation/upgrade-from-gogs.zh-cn.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "从 Gogs 升级" -slug: "upgrade-from-gogs" -sidebar_position: 101 -toc: false -draft: false -aliases: - - /zh-cn/upgrade-from-gogs -menu: - sidebar: - parent: "installation" - name: "从 Gogs 升级" - sidebar_position: 101 - identifier: "upgrade-from-gogs" ---- - -# 从 Gogs 升级 - -如果你正在运行Gogs 0.9.146以下版本,你可以平滑的升级到Gitea。该升级需要如下的步骤: - -* 使用 `gogs backup` 创建 Gogs 备份。这会创建一个名为 `gogs-backup-[时间戳].zip` 的文件,其中包含所有重要的 Gogs 数据。如果您将来想要返回到 `gogs`,您会需要这个备份文件。 -* 从 [下载页面](https://dl.gitea.com/gitea/) 下载适用于目标平台的文件。应该选择 `1.0.x` 版本。从 `gogs` 迁移到其他任何版本是不可能的。 -* 将二进制文件放置在所需的安装位置。 -* 将 `gogs/custom/conf/app.ini` 复制到 `gitea/custom/conf/app.ini`。 -* 将 `gogs/custom/` 中的自定义 `templates, public` 复制到 `gitea/custom/`。 -* 对于其他自定义文件夹,例如 `gogs/custom/conf` 中的 `gitignore, label, license, locale, readme`,将它们复制到 `gitea/custom/options`。 -* 将 `gogs/data/` 复制到 `gitea/data/`。其中包含问题附件和头像。 -* 使用 `gitea web` 启动 Gitea 进行验证。 -* 在 UI 上进入 Gitea 管理面板,运行 `Rewrite '.ssh/authorized_keys' file`。 -* 启动每个主要版本的二进制文件(例如 `1.1.4` → `1.2.3` → `1.3.4` → `1.4.2` → 等)以迁移数据库。 -* 如果自定义或配置路径已更改,请运行 `Rewrite all update hook of repositories`。 - -## 更改特定于 Gogs 的信息 - -* 将 `gogs-repositories/` 重命名为 `gitea-repositories/` -* 将 `gogs-data/` 重命名为 `gitea-data/` -* 在 `gitea/custom/conf/app.ini` 中进行更改: -从: - - ```ini - [database] - PATH = /home/:USER/gogs/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gogs-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gogs-data/avatars - [log] - ROOT_PATH = /home/:USER/gogs/log - ``` - -到: - - ```ini - [database] - PATH = /home/:USER/gitea/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gitea-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gitea-data/avatars - [log] - ROOT_PATH = /home/:USER/gitea/log - ``` - -* 使用 `gitea web` 启动 Gitea 进行验证 - -## 升级到最新版本的 `gitea` - -在成功从 `gogs` 迁移到 `gitea 1.0.x` 之后,可以通过两步过程将 `gitea` 升级到现代版本。 - -首先升级到 [`gitea 1.6.4`](https://dl.gitea.com/gitea/1.6.4/)。从 [下载页面](https://dl.gitea.com/gitea/1.6.4/) 下载适用于目标平台的文件,并替换二进制文件。至少运行一次 Gitea 并检查是否一切正常。 - -然后重复这个过程,但这次使用 [最新版本](https://dl.gitea.com/gitea/@version@/)。 - -## 从较新的 Gogs 版本升级 - -从较新的 Gogs 版本(最高到 `0.11.x`)可能也是可能的,但需要更多的工作。 -请参见 [#4286](https://github.com/go-gitea/gitea/issues/4286),其中包括各种 Gogs `0.11.x` 版本。 - -从 Gogs `0.12.x` 及更高版本升级将变得越来越困难,因为项目在配置和架构上逐渐分歧。 - -## 故障排除 - -* 如果在 `gitea/custom/templates` 文件夹中遇到与自定义模板相关的错误,请尝试逐个移除引发错误的模板。 - 它们可能与 Gitea 或更新不兼容。 - -## 将 Gitea 添加到 Unix 的启动项 - -从 [gitea/contrib](https://github.com/go-gitea/gitea/tree/main/contrib) 更新适当的文件,确保正确的环境变量。 - -对于使用 systemd 的发行版: - -* 将更新后的脚本复制到 `/etc/systemd/system/gitea.service` -* 使用以下命令将服务添加到启动项:`sudo systemctl enable gitea` -* 禁用旧的 gogs 启动脚本:`sudo systemctl disable gogs` - -对于使用 SysVinit 的发行版: - -* 将更新后的脚本复制到 `/etc/init.d/gitea` -* 使用以下命令将服务添加到启动项:`sudo rc-update add gitea` -* 禁用旧的 gogs 启动脚本:`sudo rc-update del gogs` diff --git a/docs/content/installation/upgrade-from-gogs.zh-tw.md b/docs/content/installation/upgrade-from-gogs.zh-tw.md deleted file mode 100644 index ef5737bc6d..0000000000 --- a/docs/content/installation/upgrade-from-gogs.zh-tw.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "從 Gogs 升級" -slug: "upgrade-from-gogs" -sidebar_position: 101 -toc: false -draft: false -aliases: - - /zh-tw/upgrade-from-gogs -menu: - sidebar: - parent: "installation" - name: "從 Gogs 升級" - sidebar_position: 101 - identifier: "upgrade-from-gogs" ---- - -# 從 Gogs 升級 - -**目錄** - -若您正在執行 Gogs 0.9.146 以下版本,您可以很簡單地遷移到 Gitea。 - -請參考下列步驟。在 Linux 系統上請以 Gogs 的使用者身份執行: - -- 使用 `gogs backup` 建立 Gogs 的備份。這會建立檔案 `gogs-backup-[timestamp].zip` 包含所有重要的 Gogs 資料。 - 如果稍後您要恢復到 `gogs` 時會用到它。 -- 從[下載頁](https://dl.gitea.com/gitea/)下載對應您平臺的檔案。請下載 `1.0.x` 版,從 `gogs` 遷移到其它版本是不可行的。 -- 將二進位檔放到適當的安裝位置。 -- 複製 `gogs/custom/conf/app.ini` 到 `gitea/custom/conf/app.ini`。 -- 從 `gogs/custom/` 複製自訂 `templates, public` 到 `gitea/custom/`。 -- `gogs/custom/conf` 中的其它自訂資料夾如: `gitignore, label, license, locale, readme`, - 請複製到 `gitea/custom/options`。 -- 複製 `gogs/data/` 到 `gitea/data/`。它包含了問題附件和大頭貼。 -- 以指令 `gitea web` 啟動 Gitea 驗證上列設定是否正確。 -- 從網頁 UI 進入 Gitea 管理員面板, 執行 `Rewrite '.ssh/authorized_keys' file`。 -- 執行每個主要版本的二進位檔 ( `1.1.4` → `1.2.3` → `1.3.4` → `1.4.2` → 等等 ) 以遷移資料庫。 -- 如果變更了自訂檔、設定檔路徑,請執行 `Rewrite all update hook of repositories`。 - -## 修改指定的 gogs 資訊 - -- 重新命名 `gogs-repositories/` 為 `gitea-repositories/` -- 重新命名 `gogs-data/` 為 `gitea-data/` -- 在 `gitea/custom/conf/app.ini` 中修改: - - 修改前: - - ```ini - [database] - PATH = /home/:USER/gogs/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gogs-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gogs-data/avatars - [log] - ROOT_PATH = /home/:USER/gogs/log - ``` - - 修改後: - - ```ini - [database] - PATH = /home/:USER/gitea/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gitea-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gitea-data/avatars - [log] - ROOT_PATH = /home/:USER/gitea/log - ``` - -- 執行 `gitea web` 啟動 Gitea 檢查是否正確執行 - -## 升級到最新版的 `gitea` - -成功從 `gogs` 升級到 `gitea 1.0.x` 後再用 2 個步驟即可升級到最新版的 `gitea`。 - -請先升級到 [`gitea 1.6.4`](https://dl.gitea.com/gitea/1.6.4/),先從[下載頁](https://dl.gitea.com/gitea/1.6.4/)下載 -您平臺的二進位檔取代既有的。至少執行一次 Gitea 並確認一切符合預期。 - -接著重複上述步驟,但這次請使用[最新發行版本](https://dl.gitea.com/gitea/@version@/)。 - -## 從更新版本的 Gogs 升級 - -您也可以從更新版本的 Gogs 升級,但需要更多步驟。 -請參考 [#4286](https://github.com/go-gitea/gitea/issues/4286)。 - -## 疑難排解 - -- 如果錯誤和 `gitea/custom/templates` 中 的自訂樣板有關,請試著逐一移除它們。 - 它們可能和 Gitea 或更新不相容。 - -## 在 Unix 啟動時執行 Gitea - -從 [gitea/contrib](https://github.com/go-gitea/gitea/tree/master/contrib) 更新必要的檔案以取得正確的環境變數。 - -使用 systemd 的發行版: - -- 複製新的腳本到 `/etc/systemd/system/gitea.service` -- 啟動系統時執行服務: `sudo systemctl enable gitea` -- 停用舊的 gogs 腳本: `sudo systemctl disable gogs` - -使用 SysVinit 的發行版: - -- 複製新的腳本到 `/etc/init.d/gitea` -- 啟動系統時執行服務: `sudo rc-update add gitea` -- 停用舊的 gogs 腳本: `sudo rc-update del gogs` diff --git a/docs/content/installation/windows-service.en-us.md b/docs/content/installation/windows-service.en-us.md index 90332b7c69..6c7d372549 100644 --- a/docs/content/installation/windows-service.en-us.md +++ b/docs/content/installation/windows-service.en-us.md @@ -1,6 +1,6 @@ --- date: "2016-12-21T15:00:00-02:00" -title: "Register as a Windows Service" +title: "Register as a Windows service" slug: "windows-service" sidebar_position: 50 toc: false @@ -14,8 +14,9 @@ menu: sidebar_position: 50 identifier: "windows-service" --- +# Register as a Windows service -# Prerequisites +## Prerequisites The following changes are made in C:\gitea\custom\conf\app.ini: @@ -27,7 +28,7 @@ Sets Gitea to run as the local system user. COMPUTERNAME is whatever the response is from `echo %COMPUTERNAME%` on the command line. If the response is `USER-PC` then `RUN_USER = USER-PC$` -## Use absolute paths +### Use absolute paths If you use SQLite3, change the `PATH` to include the full path: @@ -36,7 +37,7 @@ If you use SQLite3, change the `PATH` to include the full path: PATH = c:/gitea/data/gitea.db ``` -# Register as a Windows Service +## Register Gitea To register Gitea as a Windows service, open a command prompt (cmd) as an Administrator, then run the following command: @@ -51,7 +52,7 @@ Open "Windows Services", search for the service named "gitea", right-click it an "Run". If everything is OK, Gitea will be reachable on `http://localhost:3000` (or the port that was configured). -## Service startup type +### Service startup type It was observed that on loaded systems during boot Gitea service may fail to start with timeout records in Windows Event Log. In that case change startup type to `Automatic-Delayed`. This can be done during service creation, or by running config command @@ -60,7 +61,7 @@ In that case change startup type to `Automatic-Delayed`. This can be done during sc.exe config gitea start= delayed-auto ``` -## Adding startup dependencies +### Adding startup dependencies To add a startup dependency to the Gitea Windows service (eg Mysql, Mariadb), as an Administrator, then run the following command: @@ -70,9 +71,9 @@ sc.exe config gitea depend= mariadb This will ensure that when the Windows machine restarts, the automatic starting of Gitea is postponed until the database is ready and thus mitigate failed startups. -## Unregister as a service +## Unregister Gitea -To unregister Gitea as a service, open a command prompt (cmd) as an Administrator and run: +To unregister Gitea as a Windows service, open a command prompt (cmd) as an Administrator and run: ``` sc.exe delete gitea diff --git a/docs/content/installation/windows-service.fr-fr.md b/docs/content/installation/windows-service.fr-fr.md deleted file mode 100644 index d0d7606297..0000000000 --- a/docs/content/installation/windows-service.fr-fr.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Démarrer en tant que service Windows" -slug: "windows-service" -sidebar_position: 50 -toc: false -draft: false -aliases: - - /fr-fr/windows-service -menu: - sidebar: - parent: "installation" - name: "Service Windows" - sidebar_position: 50 - identifier: "windows-service" ---- - -# Activer un service Windows - -Pour activer le service Windows Gitea, ouvrez une `cmd` en tant qu'Administrateur puis utilisez la commande suivante : - -``` -sc create gitea start= auto binPath= "\"C:\gitea\gitea.exe\" web --config \"C:\gitea\custom\conf\app.ini\"" -``` - -N'oubliez pas de remplacer `C:\gitea` par le chemin que vous avez utilisé pour votre installation. - -Ensuite, ouvrez "Services Windows", puis recherchez le service `gitea`, faites un clic droit et selectionnez "Run". Si tout fonctionne, vous devriez être capable d'accèder à Gitea à l'URL `http://localhost:3000` (ou sur le port configuré si différent de 3000). - -## Désactiver un service Windows - -Pour désactiver le service Windows Gitea, ouvrez une `cmd` en tant qu'Administrateur puis utilisez la commande suivante : - -``` -sc delete gitea -``` diff --git a/docs/content/installation/windows-service.zh-tw.md b/docs/content/installation/windows-service.zh-tw.md deleted file mode 100644 index bd10b71db9..0000000000 --- a/docs/content/installation/windows-service.zh-tw.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -date: "2016-12-21T15:00:00-02:00" -title: "註冊為 Windows 服務" -slug: "windows-service" -sidebar_position: 50 -toc: false -draft: false -aliases: - - /zh-tw/windows-service -menu: - sidebar: - parent: "installation" - name: "Windows 服務" - sidebar_position: 50 - identifier: "windows-service" ---- - -# 事前準備 - -確認您的 C:\gitea\custom\conf\app.ini 中包含: - -``` -RUN_USER = COMPUTERNAME$ -``` - -設定 Gitea 以本地使用者身份執行。 - -請將在命令提示字元(cmd)執行 `echo %COMPUTERNAME%` 的結果輸入 `COMPUTERNAME`。若回應為 `USER-PC`,請輸入 `RUN_USER = USER-PC$` - -## 使用絕對路徑 - -如果您使用 sqlite3,修改 `PATH` 為完整路徑: - -``` -[database] -PATH = c:/gitea/data/gitea.db -``` - -# 註冊為 Windows 服務 - -要註冊為 Windows 服務,請先以系統管理員身份開啟命令提示字元,接著執行下列指令: - -``` -sc.exe create gitea start= auto binPath= "\"C:\gitea\gitea.exe\" web --config \"C:\gitea\custom\conf\app.ini\"" -``` - -別忘記將 `C:\gitea` 取代為您的 Gitea 安裝路徑。 - -開啟 Windows 的「服務」,並且搜尋服務名稱「gitea」,按右鍵選擇「啟動」。在瀏覽器打開 `http://localhost:3000` 就可以成功看到畫面 (如果修改過連接埠,請自行修正,3000 是預設值)。 - -## 刪除服務 - -要刪除 Gitea 服務,請用系統管理員身份開啟命令提示字元,接著執行下列指令: - -``` -sc.exe delete gitea -``` diff --git a/docs/content/installation/with-docker.fr-fr.md b/docs/content/installation/with-docker.fr-fr.md deleted file mode 100644 index 46b562e14e..0000000000 --- a/docs/content/installation/with-docker.fr-fr.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation avec Docker" -slug: "install-with-docker" -sidebar_position: 70 -toc: false -draft: false -aliases: - - /fr-fr/install-with-docker -menu: - sidebar: - parent: "installation" - name: "Docker" - sidebar_position: 70 - identifier: "install-with-docker" ---- - -# Installation avec Docker - -Nous fournissons des images Docker mises à jour automatiquement via le Docker Hub de notre organisation. C'est à vous, lors devotre déploiement, de vous assurez d'utiliser toujours la dernière version stable ou d'utiliser un autre service qui met à jour l'image Docker pour vous. - -## Données stockées sur l'hôte - -Tout d'abord, vous devez simplement récupérer l'image Docker avec la commande suivante : - -``` -docker pull gitea/gitea:latest -``` - -Pour garder vos dépôts et certaines autres données persistantes, vous devez créer un répertoire qui contiendra ces données à l'avenir. - -``` -sudo mkdir -p /var/lib/gitea -``` - -Il est temps de démarrer votre instance Docker, c'est un processus assez simple. Vous avez à définir le mappage des ports et le volume à utiliser pour la persistance de vos données : - -``` -docker run -d --name=gitea -p 10022:22 -p 10080:3000 -v /var/lib/gitea:/data gitea/gitea:latest -``` - -Vous devriez avoir une instance fonctionnelle de Gitea. Pour accèder à l'interface web, visitez l'adresse http://hostname:10080 avec votre navigateur web préféré. Si vous voulez clôner un dépôt, vous pouvez le faire avec la commande `git clone ssh://git@hostname:10022/username/repo.git`. - -## Named Volumes - -Ce guide aboutira à une installation avec les données Gitea et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité. - -### The Database - -Création du volume nommé pour la base de données : - -``` -$ docker volume create --name gitea-db-data -``` - -Une fois votre volume pret, vous pouvez récupérer l'image Docker de PostgreSQL et créer une instance. Tout comme Gitea, c'est également une image Docker basée sur Alpine Linux, Le montage des données se fera sans aucun problème. - -``` -$ docker pull postgres:alpine -$ docker run -d --name gitea-db \ - -e POSTGRES_PASSWORD= \ - -v gitea-db-data:/var/lib/postgresql/data \ - -p 5432:5432 \ - postgres:alpine -``` - -Maintenant que la base de données est démarrée, il faut la configurer. N'oubliez pas le mot de passe que vous avez choisi, vous en aurez besoin lors de l'installation de Gitea. - -``` -$ docker exec -it gitea-db psql -U postgres -psql (9.6.1) -Type "help" for help. - -postgres=# CREATE USER gitea WITH PASSWORD ''; -CREATE ROLE -postgres=# CREATE DATABASE gitea OWNER gitea; -CREATE DATABASE -postgres=# \q -$ -``` - -### Gitea - -Premièrement, le volume nommé : - -``` -$ docker volume create --name gitea-data -``` - -Puis l'instance de Gitea : - -``` -$ docker run -d --name gitea \ - --link gitea-db:gitea-db \ - --dns 10.12.10.160 \ - -p 11180:3000 \ - -p 8322:22 \ - -v gitea-data:/data \ - gitea/gitea:latest -``` - -Vous devriez maintenant avoir deux conteneurs Docker pour Gitea et PostgreSQL plus deux volumes nommés Docker. - -# Personnalisation - -Les fichier personnalisés ([voir les instructions](administration/customizing-gitea.md)) peuvent être placés dans le répertoire `/data/gitea`. - -Le fichier de configuration sera sauvegardé à l'emplacement suivant : `/data/gitea/conf/app.ini` - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/with-docker.zh-tw.md b/docs/content/installation/with-docker.zh-tw.md deleted file mode 100644 index 95f5cfffe8..0000000000 --- a/docs/content/installation/with-docker.zh-tw.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "Docker 安裝" -slug: "install-with-docker" -sidebar_position: 70 -toc: false -draft: false -aliases: - - /zh-tw/install-with-docker -menu: - sidebar: - parent: "installation" - name: "Docker 安裝" - sidebar_position: 70 - identifier: "install-with-docker" ---- - -# 用 Docker 安裝 - -我們在 Docker Hub 提供了自動更新的映像檔,它會保持最新穩定版。根據您的部屬環境來使用最新版本或用其他服務來更新 Docker 映像檔。首先您需要下載映像檔: - -``` -docker pull gitea/gitea:latest -``` - -為了儲存您的所有 Git 儲存庫資料,您應該建立一個目錄,用來存放資料的地方。 - -``` -sudo mkdir -p /var/lib/gitea -``` - -現在就可以直接啟動 Docker 容器,這是一個非常簡單的過程,您必須定義啟動連接埠,並且提供上面所建立的資料儲存路徑: - -``` -docker run -d --name=gitea -p 10022:22 -p 10080:3000 -v /var/lib/gitea:/data gitea/gitea:latest -``` - -然後 Gitea 容器已經開始運行,您可以透過個人喜愛的瀏覽器來訪問 http://hostname:10080,假如您想要開始 Clone 儲存庫,可以直接執行 `git clone ssh://git@hostname:10022/username/repo.git` 指令。 - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/search.de-de.md b/docs/content/search.de-de.md deleted file mode 100644 index 09a1ac22f2..0000000000 --- a/docs/content/search.de-de.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -date: "2019-11-12T16:00:00+02:00" -title: "Search" -slug: "search" -sidebar_position: 1 -toc: false -draft: false -aliases: - - /de-de/help/search -sitemap: - priority : 1 -layout: "search" ---- - -This file exists solely to respond to /search URL with the related `search` layout template. - -No content shown here is rendered, all content is based in the template layouts/doc/search.html - -Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/search.fr-fr.md b/docs/content/search.fr-fr.md deleted file mode 100644 index 25510b7cfd..0000000000 --- a/docs/content/search.fr-fr.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -date: "2019-11-12T16:00:00+02:00" -title: "Chercher" -slug: "search" -sidebar_position: 1 -toc: false -draft: false -aliases: - - /fr-fr/help/search -sitemap: - priority : 1 -layout: "search" ---- - -This file exists solely to respond to /search URL with the related `search` layout template. - -No content shown here is rendered, all content is based in the template layouts/doc/search.html - -Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/search.zh-tw.md b/docs/content/search.zh-tw.md deleted file mode 100644 index aa8a1ced75..0000000000 --- a/docs/content/search.zh-tw.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -date: "2019-11-12T16:00:00+02:00" -title: "搜尋" -slug: "search" -sidebar_position: 1 -toc: false -draft: false -aliases: - - /zh-tw/help/search -sitemap: - priority : 1 -layout: "search" ---- - -This file exists solely to respond to /search URL with the related `search` layout template. - -No content shown here is rendered, all content is based in the template layouts/doc/search.html - -Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/usage.zh-tw.md b/docs/content/usage.zh-tw.md deleted file mode 100644 index 795f95c726..0000000000 --- a/docs/content/usage.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-27T16:00:00+02:00" -title: "使用" -slug: "usage" -sidebar_position: 35 -toc: false -draft: false -menu: - sidebar: - name: "使用" - sidebar_position: 30 - identifier: "usage" ---- diff --git a/docs/content/usage/_index.zh-tw.md b/docs/content/usage/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/usage/actions/comparison.en-us.md b/docs/content/usage/actions/comparison.en-us.md index 64a5eff4d5..caec9d3dea 100644 --- a/docs/content/usage/actions/comparison.en-us.md +++ b/docs/content/usage/actions/comparison.en-us.md @@ -91,12 +91,6 @@ As a workaround, you can use [go-hashfiles](https://gitea.com/actions/go-hashfil ## Missing features -### Variables - -See [Variables](https://docs.github.com/en/actions/learn-github-actions/variables). - -It's under development. - ### Problem Matchers Problem Matchers are a way to scan the output of actions for a specified regex pattern and surface that information prominently in the UI. @@ -120,15 +114,17 @@ Pre and Post steps don't have their own section in the job log user interface. ### Downloading actions -Gitea Actions doesn't download actions from GitHub by default. -"By default" means that you don't specify the host in the `uses` field, like `uses: actions/checkout@v3`. -As a contrast, `uses: https://github.com/actions/checkout@v3` has specified host. +Previously (Pre 1.21.0), `[actions].DEFAULT_ACTIONS_URL` defaulted to `https://gitea.com`. +We have since restricted this option to only allow two values (`github` and `self`). +When set to `github`, the new default, Gitea will download non-fully-qualified actions from . +For example, if you use `uses: actions/checkout@v3`, it will download the checkout repository from . -The missing host will be filled with `https://gitea.com` if you don't configure it. -That means `uses: actions/checkout@v3` will download the action from [gitea.com/actions/checkout](https://gitea.com/actions/checkout), instead of [github.com/actions/checkout](https://github.com/actions/checkout). +If you want to download an action from another git hoster, you can use an absolute URL, e.g. `uses: https://gitea.com/actions/checkout@v3`. -As mentioned, it's configurable. -If you want your runners to download actions from GitHub or your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`. See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions). +If your Gitea instance is in an intranet or a restricted area, you can set the URL to `self` to only download actions from your own instance by default. +Of course, you can still use absolute URLs in workflows. + +More details about the `[actions].DEFAULT_ACTIONS_URL` configuration can be found in the [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions)。 ### Context availability diff --git a/docs/content/usage/actions/comparison.zh-cn.md b/docs/content/usage/actions/comparison.zh-cn.md index cfea7970f7..5dae75a44b 100644 --- a/docs/content/usage/actions/comparison.zh-cn.md +++ b/docs/content/usage/actions/comparison.zh-cn.md @@ -120,15 +120,13 @@ Gitea Actions目前不支持此功能。 ### 下载Actions -Gitea Actions默认不从GitHub下载Actions。 -"默认" 意味着您在`uses` 字段中不指定主机,如`uses: actions/checkout@v3`。 -相反,`uses: https://github.com/actions/checkout@v3`是有指定主机的。 +当 `[actions].DEFAULT_ACTIONS_URL` 保持默认值为 `github` 时,Gitea将会从 https://github.com 下载相对路径的actions。比如: +如果你使用 `uses: actions/checkout@v3`,Gitea将会从 https://github.com/actions/checkout.git 下载这个 actions 项目。 +如果你想要从另外一个 Git服务下载actions,你只需要使用绝对URL `uses: https://gitea.com/actions/checkout@v3` 来下载。 -如果您不进行配置,缺失的主机将填充为`https://gitea.com`。 -这意味着`uses: actions/checkout@v3`将从[gitea.com/actions/checkout](https://gitea.com/actions/checkout)下载该Action,而不是[github.com/actions/checkout](https://github.com/actions/checkout)。 +如果你的 Gitea 实例是部署在一个互联网限制的网络中,有可以使用绝对地址来下载 actions。你也可以讲配置项修改为 `[actions].DEFAULT_ACTIONS_URL = self`。这样所有的相对路径的actions引用,将不再会从 github.com 去下载,而会从这个 Gitea 实例自己的仓库中去下载。例如: `uses: actions/checkout@v3` 将会从 `[server].ROOT_URL`/actions/checkout.git 这个地址去下载 actions。 -正如前面提到的,这是可配置的。 -如果您希望您的运行程序默认从GitHub或您自己的Gitea实例下载动作,您可以通过设置`[actions].DEFAULT_ACTIONS_URL`进行配置。请参阅[配置备忘单](administration/config-cheat-sheet.md#actions-actions)。 +设置`[actions].DEFAULT_ACTIONS_URL`进行配置。请参阅[配置备忘单](administration/config-cheat-sheet.md#actions-actions)。 ### 上下文可用性 diff --git a/docs/content/usage/actions/faq.en-us.md b/docs/content/usage/actions/faq.en-us.md index 031509a033..1d59872936 100644 --- a/docs/content/usage/actions/faq.en-us.md +++ b/docs/content/usage/actions/faq.en-us.md @@ -180,3 +180,6 @@ For events supported only by GitHub, see GitHub's [documentation](https://docs.g | pull_request_review_comment | `created`, `edited` | | release | `published`, `edited` | | registry_package | `published` | + +> For `pull_request` events, in [GitHub Actions](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request), the `ref` is `refs/pull/:prNumber/merge`, which is a reference to the merge commit preview. However, Gitea has no such reference. +> Therefore, the `ref` in Gitea Actions is `refs/pull/:prNumber/head`, which points to the head of pull request rather than the preview of the merge commit. diff --git a/docs/content/usage/actions/faq.zh-cn.md b/docs/content/usage/actions/faq.zh-cn.md index f5dc8e179b..7bb79d02fc 100644 --- a/docs/content/usage/actions/faq.zh-cn.md +++ b/docs/content/usage/actions/faq.zh-cn.md @@ -180,3 +180,6 @@ defaults: | pull_request_review_comment | `created`, `edited` | | release | `published`, `edited` | | registry_package | `published` | + +> 对于 `pull_request` 事件,在 [GitHub Actions](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) 中 `ref` 是 `refs/pull/:prNumber/merge`,它指向这个拉取请求合并提交的一个预览。但是 Gitea 没有这种 reference。 +> 因此,Gitea Actions 中 `ref` 是 `refs/pull/:prNumber/head`,它指向这个拉取请求的头分支而不是合并提交的预览。 diff --git a/docs/content/usage/agit-support.en-us.md b/docs/content/usage/agit-support.en-us.md index b1643d27b3..fff9124320 100644 --- a/docs/content/usage/agit-support.en-us.md +++ b/docs/content/usage/agit-support.en-us.md @@ -18,6 +18,7 @@ menu: # Agit Setup In Gitea `1.13`, support for [agit](https://git-repo.info/en/2020/03/agit-flow-and-git-repo/) was added. +**Note**: git version 2.29 or higher is required on the server side for this to work. ## Creating PRs with Agit @@ -26,24 +27,26 @@ This can be done by pushing to the branch followed by a specific refspec (a loca The following example illustrates this: ```shell -git push origin HEAD:refs/for/master +git push origin HEAD:refs/for/main ``` The command has the following structure: - `HEAD`: The target branch -- `refs//`: The target PR type +- `origin`: The target repository (not a fork!) +- `HEAD`: The local branch containing the changes you are proposing +- `refs//`: The target PR type and configuration - `for`: Create a normal PR with `` as the target branch - - `draft`/ `for-review`: Currently ignored silently -- `/`: The target branch to open the PR + - `draft`/`for-review`: Currently ignored silently + - `/`: The branch you want your changes to be merged into - `-o `: Options for the PR - - `title`: The PR title - - `topic`: The branch name the PR should be opened for - - `description`: The PR description + - `topic`: The topic of this change. It will become the name of the branch holding the changes waiting for review. This is REQUIRED to trigger a pull request. + - `title`: The PR title (optional but recommended), only used for topics not already having an associated PR. + - `description`: The PR description (optional but recommended), only used for topics not already having an associated PR. - `force-push`: confirm force update the target branch -Here's another advanced example for creating a new PR targeting `master` with `topic`, `title`, and `description`: +Here's another advanced example for creating a new PR targeting `main` with `topic`, `title`, and `description`: ```shell -git push origin HEAD:refs/for/master -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" +git push origin HEAD:refs/for/main -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" ``` diff --git a/docs/content/usage/agit-support.zh-cn.md b/docs/content/usage/agit-support.zh-cn.md index 6a2ce1c373..54210d070b 100644 --- a/docs/content/usage/agit-support.zh-cn.md +++ b/docs/content/usage/agit-support.zh-cn.md @@ -26,7 +26,7 @@ Agit 允许在推送代码到远程仓库时创建 PR(合并请求)。 下面的示例说明了这一点: ```shell -git push origin HEAD:refs/for/master +git push origin HEAD:refs/for/main ``` 该命令的结构如下: @@ -42,8 +42,8 @@ git push origin HEAD:refs/for/master - `description`:PR 的描述 - `force-push`:确认强制更新目标分支 -下面是另一个高级示例,用于创建一个以 `topic`、`title` 和 `description` 为参数的新 PR,目标分支是 `master`: +下面是另一个高级示例,用于创建一个以 `topic`、`title` 和 `description` 为参数的新 PR,目标分支是 `main`: ```shell -git push origin HEAD:refs/for/master -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" +git push origin HEAD:refs/for/main -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" ``` diff --git a/docs/content/usage/authentication.zh-tw.md b/docs/content/usage/authentication.zh-tw.md deleted file mode 100644 index 88dc314914..0000000000 --- a/docs/content/usage/authentication.zh-tw.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "認證" -slug: "authentication" -sidebar_position: 10 -toc: false -draft: false -aliases: - - /zh-tw/authentication -menu: - sidebar: - parent: "usage" - name: "認證" - sidebar_position: 10 - identifier: "authentication" ---- - -# 認證 - -## TBD diff --git a/docs/content/usage/blame.en-us.md b/docs/content/usage/blame.en-us.md new file mode 100644 index 0000000000..7772bbc16d --- /dev/null +++ b/docs/content/usage/blame.en-us.md @@ -0,0 +1,38 @@ +--- +date: "2023-08-14T00:00:00+00:00" +title: "Blame File View" +slug: "blame" +sidebar_position: 13 +toc: false +draft: false +aliases: + - /en-us/blame +menu: + sidebar: + parent: "usage" + name: "Blame" + sidebar_position: 13 + identifier: "blame" +--- + +# Blame File View + +Gitea supports viewing the line-by-line revision history for a file also known as blame view. +You can also use [`git blame`](https://git-scm.com/docs/git-blame) on the command line to view the revision history of lines within a file. + +1. Navigate to and open the file whose line history you want to view. +1. Click the `Blame` button in the file header bar. +1. The new view shows the line-by-line revision history for a file with author and commit information on the left side. +1. To navigate to an older commit, click the ![versions](/octicon-versions.svg) icon. + +## Ignore commits in the blame view + +All revisions specified in the `.git-blame-ignore-revs` file are hidden from the blame view. +This is especially useful to hide reformatting changes and keep the benefits of `git blame`. +Lines that were changed or added by an ignored commit will be blamed on the previous commit that changed that line or nearby lines. +The `.git-blame-ignore-revs` file must be located in the root directory of the repository. +For more information like the file format, see [the `git blame --ignore-revs-file` documentation](https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt). + +### Bypassing `.git-blame-ignore-revs` in the blame view + +If the blame view for a file shows a message about ignored revisions, you can see the normal blame view by appending the url parameter `?bypass-blame-ignore=true`. diff --git a/docs/content/usage/linked-references.zh-cn.md b/docs/content/usage/linked-references.zh-cn.md index 4d63d75406..2f1b888047 100644 --- a/docs/content/usage/linked-references.zh-cn.md +++ b/docs/content/usage/linked-references.zh-cn.md @@ -113,7 +113,7 @@ menu: ## 外部跟踪器 -Gitea 支持使用外部工单跟踪器,并可以在合并请求中创建对外部托管的工单的引用。但是,如果外部跟踪器使用数字来标识工单,那么它们将与 Gitea 中托管的合并请求无法区分。为了解决这个工单,Gitea 允许使用 `!` 标记来标识合并请求。例如: +Gitea 支持使用外部工单跟踪器,并可以在合并请求中创建对外部托管的工单的引用。但是,如果外部跟踪器使用数字来标识工单,那么它们将与 Gitea 中托管的合并请求无法区分。为了解决这个问题,Gitea 允许使用 `!` 标记来标识合并请求。例如: > 这是工单 [#1234](#),并链接到外部跟踪器。 > 这是合并请求 [!1234](#),并链接到 Gitea 中的合并请求。 diff --git a/docs/content/usage/packages/alpine.en-us.md b/docs/content/usage/packages/alpine.en-us.md index 1fcbe84853..00331f3bb1 100644 --- a/docs/content/usage/packages/alpine.en-us.md +++ b/docs/content/usage/packages/alpine.en-us.md @@ -77,6 +77,7 @@ curl --user your_username:your_password_or_token \ ``` If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. + You cannot publish a file with the same name twice to a package. You must delete the existing package file first. The server responds with the following HTTP Status codes. diff --git a/docs/content/usage/packages/cargo.en-us.md b/docs/content/usage/packages/cargo.en-us.md index d5c2fab6fd..356e7ff0b9 100644 --- a/docs/content/usage/packages/cargo.en-us.md +++ b/docs/content/usage/packages/cargo.en-us.md @@ -50,10 +50,11 @@ Add the following text to the configuration file located in the current users ho default = "gitea" [registries.gitea] -index = "https://gitea.example.com/{owner}/_cargo-index.git" +index = "sparse+https://gitea.example.com/api/packages/{owner}/cargo/" # Sparse index +# index = "https://gitea.example.com/{owner}/_cargo-index.git" # Git -[net] -git-fetch-with-cli = true +# [net] +# git-fetch-with-cli = true ``` | Parameter | Description | @@ -72,6 +73,12 @@ token = "Bearer {token}" | --------- | ----------- | | `token` | Your [personal access token](development/api-usage.md#authentication) | +## Git vs Sparse + +Currently, cargo supports two ways for fetching crates in a registry: Git index & sparse index. +Sparse index is the newest method and offers better performance when updating crates compared to git. +Since Rust 1.68, sparse is the default method for crates.io. + ## Publish a package Publish a package by running the following command in your project: diff --git a/docs/content/usage/packages/cargo.zh-cn.md b/docs/content/usage/packages/cargo.zh-cn.md index 126dfd370a..285fe9c8b3 100644 --- a/docs/content/usage/packages/cargo.zh-cn.md +++ b/docs/content/usage/packages/cargo.zh-cn.md @@ -50,7 +50,8 @@ Cargo 将可用软件包的信息存储在一个存储在 git 仓库中的软件 default = "gitea" [registries.gitea] -index = "https://gitea.example.com/{owner}/_cargo-index.git" +index = "sparse+https://gitea.example.com/api/packages/{owner}/cargo/" # Sparse index +# index = "https://gitea.example.com/{owner}/_cargo-index.git" # Git [net] git-fetch-with-cli = true diff --git a/docs/content/usage/packages/composer.en-us.md b/docs/content/usage/packages/composer.en-us.md index 7fbd77ef5d..60ad52ac2d 100644 --- a/docs/content/usage/packages/composer.en-us.md +++ b/docs/content/usage/packages/composer.en-us.md @@ -25,6 +25,7 @@ To work with the Composer package registry, you can use [Composer](https://getco To publish a Composer package perform a HTTP PUT operation with the package content in the request body. The package content must be the zipped PHP project with the `composer.json` file. + You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. ``` @@ -64,7 +65,8 @@ The server responds with the following HTTP Status codes. | HTTP Status Code | Meaning | | ----------------- | ------- | | `201 Created` | The package has been published. | -| `400 Bad Request` | The package name and/or version are invalid or a package with the same name and version already exist. | +| `400 Bad Request` | The package is invalid. | +| `409 Conflict` | A package file with the same combination of parameters exists already. | ## Configuring the package registry diff --git a/docs/content/usage/packages/conan.en-us.md b/docs/content/usage/packages/conan.en-us.md index 24c7077a88..505460b319 100644 --- a/docs/content/usage/packages/conan.en-us.md +++ b/docs/content/usage/packages/conan.en-us.md @@ -63,6 +63,8 @@ For example: conan upload --remote=gitea ConanPackage/1.2@gitea/final ``` +You cannot publish a file with the same name twice to a package. You must delete the existing package or file first. + The Gitea Conan package registry has full [revision](https://docs.conan.io/en/latest/versioning/revisions.html) support. ## Install a package diff --git a/docs/content/usage/packages/conda.en-us.md b/docs/content/usage/packages/conda.en-us.md index 5b6f97679c..a256dca51c 100644 --- a/docs/content/usage/packages/conda.en-us.md +++ b/docs/content/usage/packages/conda.en-us.md @@ -63,8 +63,18 @@ curl --user your_username:your_password_or_token \ https://gitea.example.com/api/packages/testuser/conda/package-1.0.conda ``` +If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. + You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. +The server responds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `201 Created` | The package has been published. | +| `400 Bad Request` | The package is invalid. | +| `409 Conflict` | A package file with the same combination of parameters exists already. | + ## Install a package To install a package from the package registry, execute one of the following commands: diff --git a/docs/content/usage/packages/cran.en-us.md b/docs/content/usage/packages/cran.en-us.md index 68de425d21..a6b0d31856 100644 --- a/docs/content/usage/packages/cran.en-us.md +++ b/docs/content/usage/packages/cran.en-us.md @@ -68,8 +68,18 @@ curl --user your_username:your_password_or_token \ https://gitea.example.com/api/packages/testuser/cran/bin?platform=windows&rversion=4.2 ``` +If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. + You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. +The server responds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `201 Created` | The package has been published. | +| `400 Bad Request` | The package is invalid. | +| `409 Conflict` | A package file with the same combination of parameters exists already. | + ## Install a package To install a R package from the package registry, execute the following command: diff --git a/docs/content/usage/packages/debian.en-us.md b/docs/content/usage/packages/debian.en-us.md index 6bd7475dd6..9ae1dc15c0 100644 --- a/docs/content/usage/packages/debian.en-us.md +++ b/docs/content/usage/packages/debian.en-us.md @@ -77,14 +77,15 @@ curl --user your_username:your_password_or_token \ ``` If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. -You cannot publish a file with the same name twice to a package. You must delete the existing package version first. + +You cannot publish a package if a package of the same name, version, distribution, component and architecture already exists. You must delete the existing package first. The server responds with the following HTTP Status codes. | HTTP Status Code | Meaning | | ----------------- | ------- | | `201 Created` | The package has been published. | -| `400 Bad Request` | The package name, version, distribution, component or architecture are invalid. | +| `400 Bad Request` | The package is invalid. | | `409 Conflict` | A package file with the same combination of parameters exists already. | ## Delete a package diff --git a/docs/content/usage/packages/go.en-us.md b/docs/content/usage/packages/go.en-us.md index 7c4069248e..ed3c1a87e9 100644 --- a/docs/content/usage/packages/go.en-us.md +++ b/docs/content/usage/packages/go.en-us.md @@ -41,6 +41,8 @@ curl --user your_username:your_password_or_token \ If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. +You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. + The server responds with the following HTTP Status codes. | HTTP Status Code | Meaning | diff --git a/docs/content/usage/packages/swift.en-us.md b/docs/content/usage/packages/swift.en-us.md index 1bc271ddae..606fa20b36 100644 --- a/docs/content/usage/packages/swift.en-us.md +++ b/docs/content/usage/packages/swift.en-us.md @@ -67,6 +67,14 @@ curl -X PUT --user {username}:{password} \ You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. +The server responds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `201 Created` | The package has been published. | +| `400 Bad Request` | The package is invalid. | +| `409 Conflict` | A package file with the same combination of parameters exists already. | + ## Install a package To install a Swift package from the package registry, add it in the `Package.swift` file dependencies list: diff --git a/docs/content/usage/packages/vagrant.en-us.md b/docs/content/usage/packages/vagrant.en-us.md index 93cf5e3e78..baa9c2b83e 100644 --- a/docs/content/usage/packages/vagrant.en-us.md +++ b/docs/content/usage/packages/vagrant.en-us.md @@ -44,8 +44,18 @@ curl --user your_username:your_password_or_token \ https://gitea.example.com/api/packages/testuser/vagrant/test_system/1.0.0/hyperv.box ``` +If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. + You cannot publish a box if a box of the same name, version and provider already exists. You must delete the existing package first. +The server responds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `201 Created` | The package has been published. | +| `400 Bad Request` | The package is invalid. | +| `409 Conflict` | A package with the same combination of parameters exists already. | + ## Install a package To install a box from the package registry, execute the following command: diff --git a/docs/content/usage/pull-request.zh-tw.md b/docs/content/usage/pull-request.zh-tw.md deleted file mode 100644 index aeb5df5235..0000000000 --- a/docs/content/usage/pull-request.zh-tw.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -date: "2018-06-01T19:00:00+02:00" -title: "合併請求" -slug: "pull-request" -sidebar_position: 13 -toc: false -draft: false -aliases: - - /zh-tw/pull-request -menu: - sidebar: - parent: "usage" - name: "合併請求" - sidebar_position: 13 - identifier: "pull-request" ---- - -# 合併請求 - -## 「還在進行中(WIP)」的合併請求 - -將合併請求標記為還在進行中(Work In Progress, WIP)可避免意外地被合併。 -要將合併請求標記為還在進行中請在標題中使用 `WIP:` 或 `[WIP]` 前綴(不分大小寫)。這些值可在您的 `app.ini` 中設定: - -```ini -[repository.pull-request] -WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP] -``` - -網頁提示會使用第一個值作為範例。 - -## 合併請求範本 - -您可以在[問題與合併請求範本](usage/issue-pull-request-templates.md)找到更多關於合併請求範本的資訊。 diff --git a/docs/content/usage/push.zh-tw.md b/docs/content/usage/push.zh-tw.md deleted file mode 100644 index ccecbf6284..0000000000 --- a/docs/content/usage/push.zh-tw.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -date: "2020-07-06T16:00:00+02:00" -title: "使用: Push" -slug: "push" -sidebar_position: 15 -toc: false -draft: false -aliases: - - /zh-tw/push-options -menu: - sidebar: - parent: "usage" - name: "Push" - sidebar_position: 15 - identifier: "push" ---- - -There are some additional features when pushing commits to Gitea server. - -# Push Merge Hint - -When you pushing commits to a non-default branch, you will get an information from -Gitea which is a link, you can click the link and go to a compare page. It's a quick -way to create a pull request or a code review yourself in the Gitea UI. - -![Gitea Push Hint](/gitea-push-hint.png) - -# Push Options - -Gitea 從 `1.13` 版開始支援某些 [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt) -。 - -## 支援的 Options - -- `repo.private` (true|false) - 修改儲存庫的可見性。 - - 與 push-to-create 一起使用時特別有用。 - -- `repo.template` (true|false) - 修改儲存庫是否為範本儲存庫。 - -以下範例修改儲存庫的可見性為公開: - -```shell -git push -o repo.private=false -u origin main -``` - -# Push To Create - -Push to create is a feature that allows you to push to a repository that does not exist yet in Gitea. This is useful for automation and for allowing users to create repositories without having to go through the web interface. This feature is disabled by default. - -## Enabling Push To Create - -In the `app.ini` file, set `ENABLE_PUSH_CREATE_USER` to `true` and `ENABLE_PUSH_CREATE_ORG` to `true` if you want to allow users to create repositories in their own user account and in organizations they are a member of respectively. Restart Gitea for the changes to take effect. You can read more about these two options in the [Configuration Cheat Sheet](administration/config-cheat-sheet.md#repository-repository). - -## Using Push To Create - -Assuming you have a git repository in the current directory, you can push to a repository that does not exist yet in Gitea by running the following command: - -```shell -# Add the remote you want to push to -git remote add origin git@{domain}:{username}/{repo name that does not exist yet}.git - -# push to the remote -git push -u origin main -``` - -This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. diff --git a/docs/content/usage/template-repositories.en-us.md b/docs/content/usage/template-repositories.en-us.md index 4618d10f31..e57ed85cc9 100644 --- a/docs/content/usage/template-repositories.en-us.md +++ b/docs/content/usage/template-repositories.en-us.md @@ -17,9 +17,14 @@ menu: # Template Repositories -Gitea `1.11.0` and above includes template repositories, and one feature implemented with them is auto-expansion of specific variables within your template files. +Gitea (starting with version `1.11.0`) supports creating template repositories +which can be used to generate repositories based on the template, complete with +variable expansion of certain pre-defined variables. -To tell Gitea which files to expand, you must include a `template` file inside the `.gitea` directory of the template repository. +All files in the template repository are included in a generated repository from the +template except for the `.gitea/template` file. The `.gitea/template` file tells +Gitea which files are subject to the variable expansion when creating a +repository from the template. Gitea uses [gobwas/glob](https://github.com/gobwas/glob) for its glob syntax. It closely resembles a traditional `.gitignore`, however there may be slight differences. @@ -28,7 +33,7 @@ Gitea uses [gobwas/glob](https://github.com/gobwas/glob) for its glob syntax. It All paths are relative to the base of the repository ```gitignore -# All .go files, anywhere in the repository +# Expand all .go files, anywhere in the repository **.go # All text files in the text directory @@ -41,8 +46,6 @@ a/b/c/d.json **.[bB][aA][tT] ``` -**NOTE:** The `template` file will be removed from the `.gitea` directory when a repository is generated from the template. - ## Variable Expansion In any file matched by the above globs, certain variables will be expanded. diff --git a/docs/content/usage/webhooks.zh-tw.md b/docs/content/usage/webhooks.zh-tw.md deleted file mode 100644 index 666dcee83c..0000000000 --- a/docs/content/usage/webhooks.zh-tw.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "Webhook" -slug: "webhooks" -sidebar_position: 30 -toc: false -draft: false -aliases: - - /zh-tw/webhooks -menu: - sidebar: - parent: "usage" - name: "Webhook" - sidebar_position: 30 - identifier: "webhooks" ---- - -# Webhook - -Gitea 的儲存庫事件支援 web hook。這可以有儲存庫管理員在設定頁 `/:username/:reponame/settings/hooks` 中調整。Webhook 也可以按照組織調整或按照全系統調整。 -所有的事件推送都是 POST 請求。此方法目前被下列服務支援: - -- Gitea (也可以是 GET 請求) -- Gogs -- Slack -- Discord -- Dingtalk -- Telegram -- Microsoft Teams -- Feishu -- Wechatwork -- Packagist - -### 事件資訊 - -**警告**: Payload 中的 `secret` 欄位已經在 Gitea 1.13.0 棄用,並且將在 1.14.0 移除: https://github.com/go-gitea/gitea/issues/11755 - -下面是一個將由 Gitea 發送到 Payload URL 的事件資訊的範例: - -``` -X-GitHub-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473 -X-GitHub-Event: push -X-Gogs-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473 -X-Gogs-Event: push -X-Gitea-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473 -X-Gitea-Event: push -``` - -```json -{ - "secret": "3gEsCfjlV2ugRwgpU#w1*WaW*wa4NXgGmpCfkbG3", - "ref": "refs/heads/develop", - "before": "28e1879d029cb852e4844d9c718537df08844e03", - "after": "bffeb74224043ba2feb48d137756c8a9331c449a", - "compare_url": "http://localhost:3000/gitea/webhooks/compare/28e1879d029cb852e4844d9c718537df08844e03...bffeb74224043ba2feb48d137756c8a9331c449a", - "commits": [ - { - "id": "bffeb74224043ba2feb48d137756c8a9331c449a", - "message": "Webhooks Yay!", - "url": "http://localhost:3000/gitea/webhooks/commit/bffeb74224043ba2feb48d137756c8a9331c449a", - "author": { - "name": "Gitea", - "email": "someone@gitea.io", - "username": "gitea" - }, - "committer": { - "name": "Gitea", - "email": "someone@gitea.io", - "username": "gitea" - }, - "timestamp": "2017-03-13T13:52:11-04:00" - } - ], - "repository": { - "id": 140, - "owner": { - "id": 1, - "login": "gitea", - "full_name": "Gitea", - "email": "someone@gitea.io", - "avatar_url": "https://localhost:3000/avatars/1", - "username": "gitea" - }, - "name": "webhooks", - "full_name": "gitea/webhooks", - "description": "", - "private": false, - "fork": false, - "html_url": "http://localhost:3000/gitea/webhooks", - "ssh_url": "ssh://gitea@localhost:2222/gitea/webhooks.git", - "clone_url": "http://localhost:3000/gitea/webhooks.git", - "website": "", - "stars_count": 0, - "forks_count": 1, - "watchers_count": 1, - "open_issues_count": 7, - "default_branch": "master", - "created_at": "2017-02-26T04:29:06-05:00", - "updated_at": "2017-03-13T13:51:58-04:00" - }, - "pusher": { - "id": 1, - "login": "gitea", - "full_name": "Gitea", - "email": "someone@gitea.io", - "avatar_url": "https://localhost:3000/avatars/1", - "username": "gitea" - }, - "sender": { - "id": 1, - "login": "gitea", - "full_name": "Gitea", - "email": "someone@gitea.io", - "avatar_url": "https://localhost:3000/avatars/1", - "username": "gitea" - } -} -``` - -### 範例 - -此範例示範在發生推送事件時,如何使用 webhook 觸發 php 程式。 -使用下列參數在您的儲存庫設定 Webhook 中建立一個 Gitea webhook: - -- 目標 URL: http://mydomain.com/webhook.php -- HTTP 請求方法:POST -- POST Content Type:application/json -- Secret:123 -- 觸發條件:推送事件 -- 啟用:勾選 - -現在請到您的伺服器上建立 webhook.php 檔案 - -``` - \ No newline at end of file diff --git a/go.mod b/go.mod index a6e83df05a..84f42969d5 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.21 require ( code.gitea.io/actions-proto-go v0.3.1 - code.gitea.io/gitea-vet v0.2.2 - code.gitea.io/sdk/gitea v0.15.1 + code.gitea.io/gitea-vet v0.2.3 + code.gitea.io/sdk/gitea v0.16.0 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 gitea.com/go-chi/cache v0.2.0 @@ -19,7 +19,7 @@ require ( github.com/PuerkitoBio/goquery v1.8.1 github.com/alecthomas/chroma/v2 v2.9.1 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb - github.com/blevesearch/bleve/v2 v2.3.9 + github.com/blevesearch/bleve/v2 v2.3.10 github.com/bufbuild/connect-go v1.10.0 github.com/buildkite/terminal-to-html/v3 v3.9.1 github.com/caddyserver/certmagic v0.19.2 @@ -30,23 +30,23 @@ require ( github.com/djherbis/nio/v3 v3.0.1 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 github.com/dustin/go-humanize v1.0.1 - github.com/editorconfig/editorconfig-core-go/v2 v2.5.2 + github.com/editorconfig/editorconfig-core-go/v2 v2.6.0 github.com/emersion/go-imap v1.2.1 github.com/emirpasic/gods v1.18.1 github.com/ethantkoenig/rupture v1.0.1 github.com/felixge/fgprof v0.9.3 github.com/fsnotify/fsnotify v1.6.0 github.com/gliderlabs/ssh v0.3.5 - github.com/go-ap/activitypub v0.0.0-20230807182453-602f717f6ca3 + github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 github.com/go-co-op/gocron v1.31.1 - github.com/go-enry/go-enry/v2 v2.8.4 + github.com/go-enry/go-enry/v2 v2.8.6 github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e - github.com/go-git/go-billy/v5 v5.4.1 - github.com/go-git/go-git/v5 v5.8.1 - github.com/go-ldap/ldap/v3 v3.4.5 + github.com/go-git/go-billy/v5 v5.5.0 + github.com/go-git/go-git/v5 v5.9.0 + github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-sql-driver/mysql v1.7.1 github.com/go-swagger/go-swagger v0.30.5 github.com/go-testfixtures/testfixtures/v3 v3.9.0 @@ -56,40 +56,40 @@ require ( github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/golang-jwt/jwt/v5 v5.0.0 github.com/google/go-github/v53 v53.2.0 - github.com/google/pprof v0.0.0-20230901174712-0191c66da455 + github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 github.com/google/uuid v1.3.1 github.com/gorilla/feeds v1.1.1 github.com/gorilla/sessions v1.2.1 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/golang-lru/v2 v2.0.6 + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/huandu/xstrings v1.4.0 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 - github.com/jhillyerd/enmime v1.0.0 + github.com/jhillyerd/enmime v1.0.1 github.com/json-iterator/go v1.1.12 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 - github.com/klauspost/compress v1.16.7 + github.com/klauspost/compress v1.17.0 github.com/klauspost/cpuid/v2 v2.2.5 github.com/lib/pq v1.10.9 - github.com/markbates/goth v1.77.0 + github.com/markbates/goth v1.78.0 github.com/mattn/go-isatty v0.0.19 github.com/mattn/go-sqlite3 v1.14.17 - github.com/meilisearch/meilisearch-go v0.25.0 + github.com/meilisearch/meilisearch-go v0.25.1 github.com/mholt/archiver/v3 v3.5.1 - github.com/microcosm-cc/bluemonday v1.0.25 + github.com/microcosm-cc/bluemonday v1.0.26 github.com/minio/minio-go/v7 v7.0.63 github.com/minio/sha256-simd v1.0.1 - github.com/msteinert/pam v1.1.0 - github.com/nektos/act v0.2.48 + github.com/msteinert/pam v1.2.0 + github.com/nektos/act v0.2.52 github.com/niklasfasching/go-org v1.7.0 github.com/olivere/elastic/v7 v7.0.32 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc4 + github.com/opencontainers/image-spec v1.1.0-rc5 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 - github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_golang v1.17.0 github.com/quasoft/websspi v1.1.2 - github.com/redis/go-redis/v9 v9.1.0 + github.com/redis/go-redis/v9 v9.2.1 github.com/robfig/cron/v3 v3.0.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/sassoftware/go-rpmutils v0.2.0 @@ -100,20 +100,20 @@ require ( github.com/tstranex/u2f v1.0.0 github.com/ulikunitz/xz v0.5.11 github.com/urfave/cli/v2 v2.25.7 - github.com/xanzy/go-gitlab v0.91.0 + github.com/xanzy/go-gitlab v0.93.1 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yohcop/openid-go v1.0.1 github.com/yuin/goldmark v1.5.6 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 - golang.org/x/crypto v0.12.0 - golang.org/x/image v0.11.0 - golang.org/x/net v0.14.0 - golang.org/x/oauth2 v0.11.0 - golang.org/x/sys v0.12.0 + golang.org/x/crypto v0.14.0 + golang.org/x/image v0.13.0 + golang.org/x/net v0.17.0 + golang.org/x/oauth2 v0.13.0 + golang.org/x/sys v0.13.0 golang.org/x/text v0.13.0 - golang.org/x/tools v0.12.0 - google.golang.org/grpc v1.57.0 + golang.org/x/tools v0.14.0 + google.golang.org/grpc v1.58.3 google.golang.org/protobuf v1.31.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/ini.v1 v1.67.0 @@ -121,23 +121,23 @@ require ( mvdan.cc/xurls/v2 v2.5.0 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 xorm.io/builder v0.3.13 - xorm.io/xorm v1.3.3-0.20230725140238-59b727260d35 + xorm.io/xorm v1.3.4-0.20231017094142-dbe499091a7e ) require ( - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute v1.23.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect dario.cat/mergo v1.0.0 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect github.com/ClickHouse/ch-go v0.58.2 // indirect - github.com/ClickHouse/clickhouse-go/v2 v2.13.4 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.14.3 // indirect github.com/DataDog/zstd v1.5.5 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect - github.com/RoaringBitmap/roaring v1.5.0 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect + github.com/RoaringBitmap/roaring v1.6.0 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect @@ -145,40 +145,42 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.8.0 // indirect - github.com/blevesearch/bleve_index_api v1.0.5 // indirect - github.com/blevesearch/geo v0.1.17 // indirect + github.com/bits-and-blooms/bitset v1.9.0 // indirect + github.com/blevesearch/bleve_index_api v1.0.6 // indirect + github.com/blevesearch/geo v0.1.18 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/mmap-go v1.0.4 // indirect - github.com/blevesearch/scorch_segment_api/v2 v2.1.5 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.1.6 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect github.com/blevesearch/vellum v1.0.10 // indirect - github.com/blevesearch/zapx/v11 v11.3.9 // indirect - github.com/blevesearch/zapx/v12 v12.3.9 // indirect - github.com/blevesearch/zapx/v13 v13.3.9 // indirect - github.com/blevesearch/zapx/v14 v14.3.9 // indirect - github.com/blevesearch/zapx/v15 v15.3.12 // indirect + github.com/blevesearch/zapx/v11 v11.3.10 // indirect + github.com/blevesearch/zapx/v12 v12.3.10 // indirect + github.com/blevesearch/zapx/v13 v13.3.10 // indirect + github.com/blevesearch/zapx/v14 v14.3.10 // indirect + github.com/blevesearch/zapx/v15 v15.3.13 // indirect github.com/boombuler/barcode v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285 // indirect + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.2.1 // indirect github.com/couchbase/goutils v0.1.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.10.0 // indirect github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead // indirect github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fxamacker/cbor/v2 v2.5.0 // indirect - github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea // indirect - github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.6.1 // indirect @@ -209,9 +211,7 @@ require ( github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.16 // indirect @@ -230,7 +230,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mholt/acmez v1.2.0 // indirect - github.com/miekg/dns v1.1.55 // indirect + github.com/miekg/dns v1.1.56 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -243,37 +243,37 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/gomega v1.27.8 // indirect github.com/paulmach/orb v0.10.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect - github.com/rhysd/actionlint v1.6.25 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rhysd/actionlint v1.6.26 // indirect github.com/rivo/uniseg v0.4.4 // indirect - github.com/robfig/cron v1.2.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.0 // indirect - github.com/spf13/afero v1.9.5 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.16.0 // indirect + github.com/spf13/viper v1.17.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.49.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/fastjson v1.6.4 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect @@ -284,16 +284,17 @@ require ( github.com/zeebo/blake3 v0.2.3 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.mongodb.org/mongo-driver v1.12.1 // indirect - go.opentelemetry.io/otel v1.17.0 // indirect - go.opentelemetry.io/otel/trace v1.17.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.25.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sync v0.4.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -303,7 +304,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 -replace github.com/nektos/act => gitea.com/gitea/act v0.243.4 +replace github.com/nektos/act => gitea.com/gitea/act v0.2.51 exclude github.com/gofrs/uuid v3.2.0+incompatible diff --git a/go.sum b/go.sum index 5e32daf0c1..379533b08c 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -42,11 +42,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= code.gitea.io/actions-proto-go v0.3.1 h1:PMyiQtBKb8dNnpEO2R5rcZdXSis+UQZVo/SciMtR1aU= code.gitea.io/actions-proto-go v0.3.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= -code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -code.gitea.io/gitea-vet v0.2.2 h1:TEOV/Glf38iGmKzKP0EB++Z5OSL4zGg3RrAvlwaMuvk= -code.gitea.io/gitea-vet v0.2.2/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= -code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= +code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI= +code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= +code.gitea.io/sdk/gitea v0.16.0 h1:gAfssETO1Hv9QbE+/nhWu7EjoFQYKt6kPoyDytQgw00= +code.gitea.io/sdk/gitea v0.16.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -54,8 +53,8 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/gitea/act v0.243.4 h1:MuBHBLCJfpa6mzwwvs4xqQynrSP2RRzpHpWfTV16PmI= -gitea.com/gitea/act v0.243.4/go.mod h1:mabw6AZAiDgxGlK83orWLrNERSPvgBJzEUS3S7u2bHI= +gitea.com/gitea/act v0.2.51 h1:gXc/B4OlTciTTzAx9cmNyw04n2SDO7exPjAsR5Idu+c= +gitea.com/gitea/act v0.2.51/go.mod h1:CoaX2053jqBlD6JMgu4d4UgFL/rp2I14Kt5mMqcs0Z0= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0= @@ -85,8 +84,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= -github.com/ClickHouse/clickhouse-go/v2 v2.13.4 h1:NcvYN9ONZn3vlPMfQVUBSG5LKz+1y2wk4vaaz5QZXIg= -github.com/ClickHouse/clickhouse-go/v2 v2.13.4/go.mod h1:u1AUh8E0XqN1sU1EDzbiGLTI4KWOd+lOHimNSsdyJec= +github.com/ClickHouse/clickhouse-go/v2 v2.14.3 h1:s9SuU3PfJrfJ4SDbVRo6XM2ZWlr7efvW9Z/ppUpE1vo= +github.com/ClickHouse/clickhouse-go/v2 v2.14.3/go.mod h1:qdw8IMGH4Y+PedKlf9QEhFO1ATTSFhh4exQRVIa3y2A= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -104,16 +103,16 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= +github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I= -github.com/RoaringBitmap/roaring v1.5.0 h1:V0VCSiHjroItEYCM3guC8T83ehi5QMt3oM9EefTTOms= -github.com/RoaringBitmap/roaring v1.5.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= +github.com/RoaringBitmap/roaring v1.6.0 h1:dc7kRiroETgJcHhWX6BerXkZz2b3JgLGg9nTURJL/og= +github.com/RoaringBitmap/roaring v1.6.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= @@ -143,24 +142,22 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= -github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.9.0 h1:g1YivPG8jOtrN013Fe8OBXubkiTwvm7/vG2vXz03ANU= +github.com/bits-and-blooms/bitset v1.9.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM= -github.com/blevesearch/bleve/v2 v2.3.9 h1:pUMvK0mxAexqasZcVj8lazmWnEW5XiV0tASIqANiNTQ= -github.com/blevesearch/bleve/v2 v2.3.9/go.mod h1:1PibElcjlQMQHF9uS9mRv58ODQgj4pCWHA1Wfd+qagU= +github.com/blevesearch/bleve/v2 v2.3.10 h1:z8V0wwGoL4rp7nG/O3qVVLYxUqCbEwskMt4iRJsPLgg= +github.com/blevesearch/bleve/v2 v2.3.10/go.mod h1:RJzeoeHC+vNHsoLR54+crS1HmOWpnH87fL70HAUCzIA= github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= -github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw= -github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms= -github.com/blevesearch/geo v0.1.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA= -github.com/blevesearch/geo v0.1.17/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM= +github.com/blevesearch/bleve_index_api v1.0.6 h1:gyUUxdsrvmW3jVhhYdCVL6h9dCjNT/geNU7PxGn37p8= +github.com/blevesearch/bleve_index_api v1.0.6/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms= +github.com/blevesearch/geo v0.1.18 h1:Np8jycHTZ5scFe7VEPLrDoHnnb9C4j636ue/CGrhtDw= +github.com/blevesearch/geo v0.1.18/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM= github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= @@ -169,8 +166,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU= -github.com/blevesearch/scorch_segment_api/v2 v2.1.5 h1:1g713kpCQZ8u4a3stRGBfrwVOuGRnmxOVU5MQkUPrHU= -github.com/blevesearch/scorch_segment_api/v2 v2.1.5/go.mod h1:f2nOkKS1HcjgIWZgDAErgBdxmr2eyt0Kn7IY+FU1Xe4= +github.com/blevesearch/scorch_segment_api/v2 v2.1.6 h1:CdekX/Ob6YCYmeHzD72cKpwzBjvkOGegHOqhAkXp6yA= +github.com/blevesearch/scorch_segment_api/v2 v2.1.6/go.mod h1:nQQYlp51XvoSVxcciBjtvuHPIVjlWrN1hX4qwK2cqdc= github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= @@ -184,30 +181,30 @@ github.com/blevesearch/vellum v1.0.4/go.mod h1:cMhywHI0de50f7Nj42YgvyD6bFJ2WkNRv github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI= github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= github.com/blevesearch/zapx/v11 v11.2.0/go.mod h1:gN/a0alGw1FZt/YGTo1G6Z6XpDkeOfujX5exY9sCQQM= -github.com/blevesearch/zapx/v11 v11.3.9 h1:y3ijS4h4MJdmQ07MHASxat4owAixreK2xdo76w9ncrw= -github.com/blevesearch/zapx/v11 v11.3.9/go.mod h1:jcAYnQwlr+LqD2vLjDWjWiZDXDXGFqPbpPDRTd3XmS4= +github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk= +github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= github.com/blevesearch/zapx/v12 v12.2.0/go.mod h1:fdjwvCwWWwJW/EYTYGtAp3gBA0geCYGLcVTtJEZnY6A= -github.com/blevesearch/zapx/v12 v12.3.9 h1:MXGLlZ03oxXH3DMJTZaBaRj2xb6t4wQVZeZK/wu1M6w= -github.com/blevesearch/zapx/v12 v12.3.9/go.mod h1:QXCMwmOkdLnMDgTN1P4CcuX5F851iUOtOwXbw0HMBYs= +github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s= +github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= github.com/blevesearch/zapx/v13 v13.2.0/go.mod h1:o5rAy/lRS5JpAbITdrOHBS/TugWYbkcYZTz6VfEinAQ= -github.com/blevesearch/zapx/v13 v13.3.9 h1:+VAz9V0VmllHXlZV4DCvfYj0nqaZHgF3MeEHwOyRBwQ= -github.com/blevesearch/zapx/v13 v13.3.9/go.mod h1:s+WjNp4WSDtrBVBpa37DUOd7S/Gr/jTZ7ST/MbCVj/0= +github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8= +github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= github.com/blevesearch/zapx/v14 v14.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g= -github.com/blevesearch/zapx/v14 v14.3.9 h1:wuqxATgsTCNHM9xsOFOeFp8H2heZ/gMX/tsl9lRK8U4= -github.com/blevesearch/zapx/v14 v14.3.9/go.mod h1:MWZ4v8AzFBRurhDzkLvokFW8ljcq9Evm27mkWe8OGbM= +github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU= +github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A= -github.com/blevesearch/zapx/v15 v15.3.12 h1:w/kU9aHyfMDEdwHGZzCiakC3HZ9z5gYlXaALDC4Dct8= -github.com/blevesearch/zapx/v15 v15.3.12/go.mod h1:tx53gDJS/7Oa3Je820cmVurqCuJ4dqdAy1kiDMV/IUo= +github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ= +github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285 h1:Dr+ezPI5ivhMn/3WOoB86XzMhie146DNaBbhaQWZHMY= -github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg= github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8= github.com/buildkite/terminal-to-html/v3 v3.9.1 h1:8SOCKFK9ntpYvPE3yUAXHiZYdQI4xf9o9S3wOX7x12A= @@ -253,14 +250,19 @@ github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9B github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE= github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= @@ -287,12 +289,12 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= -github.com/editorconfig/editorconfig-core-go/v2 v2.5.2 h1:Z/G8cwnwOzGgTtvTutUhWPJ1ySUzuermioYMra7TuDQ= -github.com/editorconfig/editorconfig-core-go/v2 v2.5.2/go.mod h1:DoNm5QtDjTkizv0Oo1O+OJ92feoyUz3V4StZpOmP69E= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.0 h1:5O8paxMLmi/5ONoKXzWNYxoSZU7+ITVbGcPga0IrzfE= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.0/go.mod h1:hdTKe+hwa3mMnMn4JUQziT+yc3pF+6EVmK2LPbLZthE= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA= github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= @@ -331,14 +333,14 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-ap/activitypub v0.0.0-20230807182453-602f717f6ca3 h1:Lxp2XxNZ+2bqlpJbKeQme5kMf9NqPJQB1l2eZLJ7W/A= -github.com/go-ap/activitypub v0.0.0-20230807182453-602f717f6ca3/go.mod h1:qw0WNf+PTG69Xu6mVqUluDuKl1VwVYdgntOZQFBZQ48= -github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea h1:ywGtLGVjJjMrq4mu35Qmu+NtlhlTk/gTayE6Bb4tQZk= -github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea/go.mod h1:SaTNjEEkp0q+w3pUS1ccyEL/lUrHteORlDq/e21mCc8= +github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo= +github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI= +github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0= +github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7/go.mod h1:5x8a6P/dhmMGFxWLcyYlyOuJ2lRNaHGhRv+yu8BaTSI= github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw= github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA= -github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= -github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= @@ -347,30 +349,31 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-co-op/gocron v1.31.1 h1:LZAuBlU0t3SPGUMJGhrJ6VuCc3CsrYzkzicygvVWlfA= github.com/go-co-op/gocron v1.31.1/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y= -github.com/go-enry/go-enry/v2 v2.8.4 h1:QrY3hx/RiqCJJRbdU0MOcjfTM1a586J0WSooqdlJIhs= -github.com/go-enry/go-enry/v2 v2.8.4/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= +github.com/go-enry/go-enry/v2 v2.8.6 h1:T6ljs5+qNiUTDqpfK5GUD5EvLNdDbf804u8iC30vw7U= +github.com/go-enry/go-enry/v2 v2.8.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= -github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= +github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= +github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= -github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= @@ -425,8 +428,8 @@ github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewe github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= -github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= github.com/go-webauthn/webauthn v0.8.6 h1:bKMtL1qzd2WTFkf1mFTVbreYrwn7dsYmEPjTq6QN90E= @@ -509,6 +512,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -554,8 +558,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/pprof v0.0.0-20230901174712-0191c66da455 h1:YhRUmI1ttDC4sxKY2V62BTI8hCXnyZBV9h38eAanInE= -github.com/google/pprof v0.0.0-20230901174712-0191c66da455/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -588,22 +592,17 @@ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= -github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -677,8 +676,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhillyerd/enmime v1.0.0 h1:8swYgO1fm68PllCKz5jiLzgD3axNUS388jr6BtRSsl8= -github.com/jhillyerd/enmime v1.0.0/go.mod h1:EktNOa/V6ka9yCrfoB2uxgefp1lno6OVdszW0iQ5LnM= +github.com/jhillyerd/enmime v1.0.1 h1:y6RyqIgBOI2hIinOXIzmeB+ITRVls0zTJIm5GwgXnjE= +github.com/jhillyerd/enmime v1.0.1/go.mod h1:LMMbm6oTlzWHghPavqHtOrP/NosVv3l42CUrZjn03/Q= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= @@ -707,8 +706,8 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -761,8 +760,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE= github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o= -github.com/markbates/goth v1.77.0 h1:s3scqnWv/Zq/a5M766V0FKsLfOdFNdh/HEkuWCKbvT8= -github.com/markbates/goth v1.77.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= +github.com/markbates/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY= +github.com/markbates/goth v1.78.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= @@ -787,16 +786,16 @@ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/meilisearch/meilisearch-go v0.25.0 h1:xIp+8YWterHuDvpdYlwQ4Qp7im3JlRHmSKiP0NvjyXs= -github.com/meilisearch/meilisearch-go v0.25.0/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= +github.com/meilisearch/meilisearch-go v0.25.1 h1:D5wY22sn5kkpRH3uYMGlwltdUEq5regIFmO7awHz3Vo= +github.com/meilisearch/meilisearch-go v0.25.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= -github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ= @@ -828,8 +827,8 @@ github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkF github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/msteinert/pam v1.1.0 h1:VhLun/0n0kQYxiRBJJvVpC2jR6d21SWJFjpvUVj20Kc= -github.com/msteinert/pam v1.1.0/go.mod h1:M4FPeAW8g2ITO68W8gACDz13NDJyOQM9IQsQhrR6TOI= +github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE= +github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek= github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= @@ -857,12 +856,12 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= -github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= @@ -884,34 +883,33 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw= github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rhysd/actionlint v1.6.25 h1:0Is99a51w1iocdxKUzNYiBNwjoSlO2Klqzll98joVj4= -github.com/rhysd/actionlint v1.6.25/go.mod h1:Q+MtZKm1MdmJ9woOSKxLscMW7kU44/PShvjNy5ZKHA8= +github.com/rhysd/actionlint v1.6.26 h1:zi7jPZf3Ks14gCXYAAL47uBziyFlX7+Xwilqhexct9g= +github.com/rhysd/actionlint v1.6.26/go.mod h1:TIj1DlCgtYLOv5CH9wCK+WJTOr1qAdnFzkGi0IgSCO4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -930,6 +928,10 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE= @@ -955,8 +957,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= -github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck= @@ -964,9 +966,11 @@ github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -974,14 +978,12 @@ github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= @@ -1026,17 +1028,16 @@ github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6S github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE= -github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= -github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xanzy/go-gitlab v0.91.0 h1:o8S6TeaPq/b1cGcZ0UWnj4cBP5urtBdYqwmoLZyCdDY= -github.com/xanzy/go-gitlab v0.91.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xanzy/go-gitlab v0.93.1 h1:f7J33cw/P9b/8paIOoH0F3H+TFrswvWHs6yUgoTp9LY= +github.com/xanzy/go-gitlab v0.93.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -1097,10 +1098,10 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= -go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= -go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= -go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= -go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1120,8 +1121,8 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1137,10 +1138,12 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1148,8 +1151,9 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1160,10 +1164,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= +golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= +golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1187,8 +1193,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1237,8 +1243,9 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1248,8 +1255,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1264,8 +1271,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1339,8 +1346,10 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1349,8 +1358,10 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1365,7 +1376,6 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1440,8 +1450,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1474,8 +1484,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1513,8 +1524,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a h1:a2MQQVoTo96JC9PMGtGBymLp7+/RzpFc2yX/9WfFg1c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1532,8 +1543,8 @@ google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1637,5 +1648,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1: xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= -xorm.io/xorm v1.3.3-0.20230725140238-59b727260d35 h1:qjhRBMFRr3IdT2YR8VByOMfko8vxINiOPeOc267zuPI= -xorm.io/xorm v1.3.3-0.20230725140238-59b727260d35/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo= +xorm.io/xorm v1.3.4-0.20231017094142-dbe499091a7e h1:oL+ktEW+iSrCXU6IauJ9csZFATEmawkhsq/MAoHTdwo= +xorm.io/xorm v1.3.4-0.20231017094142-dbe499091a7e/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo= diff --git a/models/actions/main_test.go b/models/actions/main_test.go new file mode 100644 index 0000000000..5d5089e3bb --- /dev/null +++ b/models/actions/main_test.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + FixtureFiles: []string{ + "action_runner_token.yml", + }, + }) +} diff --git a/models/actions/run_job_list.go b/models/actions/run_job_list.go index 047bf64410..a166396694 100644 --- a/models/actions/run_job_list.go +++ b/models/actions/run_job_list.go @@ -42,7 +42,7 @@ func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error { for _, r := range runs { runsList = append(runsList, r) } - return runsList.LoadRepos() + return runsList.LoadRepos(ctx) } return nil } diff --git a/models/actions/run_list.go b/models/actions/run_list.go index db36f6df98..cd053ea7b5 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -52,9 +52,9 @@ func (runs RunList) LoadTriggerUser(ctx context.Context) error { return nil } -func (runs RunList) LoadRepos() error { +func (runs RunList) LoadRepos(ctx context.Context) error { repoIDs := runs.GetRepoIDs() - repos, err := repo_model.GetRepositoriesMapByIDs(repoIDs) + repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs) if err != nil { return err } diff --git a/models/actions/runner.go b/models/actions/runner.go index c79a6827fc..ec6b49cf16 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -189,6 +189,12 @@ func (opts FindRunnerOptions) toOrder() string { return "last_online ASC" case "alphabetically": return "name ASC" + case "reversealphabetically": + return "name DESC" + case "newest": + return "id DESC" + case "oldest": + return "id ASC" } return "last_online DESC" } diff --git a/models/actions/runner_token.go b/models/actions/runner_token.go index fabd6c644c..ccd9bbccb3 100644 --- a/models/actions/runner_token.go +++ b/models/actions/runner_token.go @@ -22,7 +22,7 @@ type ActionRunnerToken struct { Owner *user_model.User `xorm:"-"` RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global Repo *repo_model.Repository `xorm:"-"` - IsActive bool + IsActive bool // true means it can be used Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` @@ -57,7 +57,7 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string return err } -// NewRunnerToken creates a new runner token +// NewRunnerToken creates a new active runner token and invalidate all old tokens func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { token, err := util.CryptoRandomString(40) if err != nil { @@ -66,17 +66,27 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo runnerToken := &ActionRunnerToken{ OwnerID: ownerID, RepoID: repoID, - IsActive: false, + IsActive: true, Token: token, } - _, err = db.GetEngine(ctx).Insert(runnerToken) - return runnerToken, err + + return runnerToken, db.WithTx(ctx, func(ctx context.Context) error { + if _, err := db.GetEngine(ctx).Where("owner_id =? AND repo_id = ?", ownerID, repoID).Cols("is_active").Update(&ActionRunnerToken{ + IsActive: false, + }); err != nil { + return err + } + + _, err = db.GetEngine(ctx).Insert(runnerToken) + return err + }) } -// GetUnactivatedRunnerToken returns a unactivated runner token -func GetUnactivatedRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { +// GetLatestRunnerToken returns the latest runner token +func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { var runnerToken ActionRunnerToken - has, err := db.GetEngine(ctx).Where("owner_id=? AND repo_id=? AND is_active=?", ownerID, repoID, false).OrderBy("id DESC").Get(&runnerToken) + has, err := db.GetEngine(ctx).Where("owner_id=? AND repo_id=?", ownerID, repoID). + OrderBy("id DESC").Get(&runnerToken) if err != nil { return nil, err } else if !has { diff --git a/models/actions/runner_token_test.go b/models/actions/runner_token_test.go new file mode 100644 index 0000000000..e85e99abe5 --- /dev/null +++ b/models/actions/runner_token_test.go @@ -0,0 +1,40 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestGetLatestRunnerToken(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) + expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) + assert.NoError(t, err) + assert.EqualValues(t, token, expectedToken) +} + +func TestNewRunnerToken(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + token, err := NewRunnerToken(db.DefaultContext, 1, 0) + assert.NoError(t, err) + expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) + assert.NoError(t, err) + assert.EqualValues(t, token, expectedToken) +} + +func TestUpdateRunnerToken(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) + token.IsActive = true + assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token)) + expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) + assert.NoError(t, err) + assert.EqualValues(t, token, expectedToken) +} diff --git a/models/actions/schedule.go b/models/actions/schedule.go index b0bc40dadc..34d23f1c01 100644 --- a/models/actions/schedule.go +++ b/models/actions/schedule.go @@ -41,15 +41,15 @@ func init() { } // GetSchedulesMapByIDs returns the schedules by given id slice. -func GetSchedulesMapByIDs(ids []int64) (map[int64]*ActionSchedule, error) { +func GetSchedulesMapByIDs(ctx context.Context, ids []int64) (map[int64]*ActionSchedule, error) { schedules := make(map[int64]*ActionSchedule, len(ids)) - return schedules, db.GetEngine(db.DefaultContext).In("id", ids).Find(&schedules) + return schedules, db.GetEngine(ctx).In("id", ids).Find(&schedules) } // GetReposMapByIDs returns the repos by given id slice. -func GetReposMapByIDs(ids []int64) (map[int64]*repo_model.Repository, error) { +func GetReposMapByIDs(ctx context.Context, ids []int64) (map[int64]*repo_model.Repository, error) { repos := make(map[int64]*repo_model.Repository, len(ids)) - return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos) + return repos, db.GetEngine(ctx).In("id", ids).Find(&repos) } var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) diff --git a/models/actions/schedule_list.go b/models/actions/schedule_list.go index e873c05ec3..ddd9a1321e 100644 --- a/models/actions/schedule_list.go +++ b/models/actions/schedule_list.go @@ -49,9 +49,9 @@ func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error { return nil } -func (schedules ScheduleList) LoadRepos() error { +func (schedules ScheduleList) LoadRepos(ctx context.Context) error { repoIDs := schedules.GetRepoIDs() - repos, err := repo_model.GetRepositoriesMapByIDs(repoIDs) + repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs) if err != nil { return err } diff --git a/models/actions/schedule_spec_list.go b/models/actions/schedule_spec_list.go index d379490b4e..6bf91cf819 100644 --- a/models/actions/schedule_spec_list.go +++ b/models/actions/schedule_spec_list.go @@ -23,9 +23,9 @@ func (specs SpecList) GetScheduleIDs() []int64 { return ids.Values() } -func (specs SpecList) LoadSchedules() error { +func (specs SpecList) LoadSchedules(ctx context.Context) error { scheduleIDs := specs.GetScheduleIDs() - schedules, err := GetSchedulesMapByIDs(scheduleIDs) + schedules, err := GetSchedulesMapByIDs(ctx, scheduleIDs) if err != nil { return err } @@ -34,7 +34,7 @@ func (specs SpecList) LoadSchedules() error { } repoIDs := specs.GetRepoIDs() - repos, err := GetReposMapByIDs(repoIDs) + repos, err := GetReposMapByIDs(ctx, repoIDs) if err != nil { return err } @@ -53,9 +53,9 @@ func (specs SpecList) GetRepoIDs() []int64 { return ids.Values() } -func (specs SpecList) LoadRepos() error { +func (specs SpecList) LoadRepos(ctx context.Context) error { repoIDs := specs.GetRepoIDs() - repos, err := repo_model.GetRepositoriesMapByIDs(repoIDs) + repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs) if err != nil { return err } @@ -95,7 +95,7 @@ func FindSpecs(ctx context.Context, opts FindSpecOptions) (SpecList, int64, erro return nil, 0, err } - if err := specs.LoadSchedules(); err != nil { + if err := specs.LoadSchedules(ctx); err != nil { return nil, 0, err } return specs, total, nil diff --git a/models/activities/action.go b/models/activities/action.go index 07c8053425..1bfd62438b 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -140,7 +140,7 @@ func (at ActionType) InActions(actions ...string) bool { // used in template render. type Action struct { ID int64 `xorm:"pk autoincr"` - UserID int64 // Receiver user id. + UserID int64 `xorm:"INDEX"` // Receiver user id. OpType ActionType ActUserID int64 // Action user id. ActUser *user_model.User `xorm:"-"` @@ -208,91 +208,91 @@ func (a *Action) loadRepo(ctx context.Context) { } // GetActFullName gets the action's user full name. -func (a *Action) GetActFullName() string { - a.LoadActUser(db.DefaultContext) +func (a *Action) GetActFullName(ctx context.Context) string { + a.LoadActUser(ctx) return a.ActUser.FullName } // GetActUserName gets the action's user name. -func (a *Action) GetActUserName() string { - a.LoadActUser(db.DefaultContext) +func (a *Action) GetActUserName(ctx context.Context) string { + a.LoadActUser(ctx) return a.ActUser.Name } // ShortActUserName gets the action's user name trimmed to max 20 // chars. -func (a *Action) ShortActUserName() string { - return base.EllipsisString(a.GetActUserName(), 20) +func (a *Action) ShortActUserName(ctx context.Context) string { + return base.EllipsisString(a.GetActUserName(ctx), 20) } // GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank. -func (a *Action) GetDisplayName() string { +func (a *Action) GetDisplayName(ctx context.Context) string { if setting.UI.DefaultShowFullName { - trimmedFullName := strings.TrimSpace(a.GetActFullName()) + trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx)) if len(trimmedFullName) > 0 { return trimmedFullName } } - return a.ShortActUserName() + return a.ShortActUserName(ctx) } // GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME -func (a *Action) GetDisplayNameTitle() string { +func (a *Action) GetDisplayNameTitle(ctx context.Context) string { if setting.UI.DefaultShowFullName { - return a.ShortActUserName() + return a.ShortActUserName(ctx) } - return a.GetActFullName() + return a.GetActFullName(ctx) } // GetRepoUserName returns the name of the action repository owner. -func (a *Action) GetRepoUserName() string { - a.loadRepo(db.DefaultContext) +func (a *Action) GetRepoUserName(ctx context.Context) string { + a.loadRepo(ctx) return a.Repo.OwnerName } // ShortRepoUserName returns the name of the action repository owner // trimmed to max 20 chars. -func (a *Action) ShortRepoUserName() string { - return base.EllipsisString(a.GetRepoUserName(), 20) +func (a *Action) ShortRepoUserName(ctx context.Context) string { + return base.EllipsisString(a.GetRepoUserName(ctx), 20) } // GetRepoName returns the name of the action repository. -func (a *Action) GetRepoName() string { - a.loadRepo(db.DefaultContext) +func (a *Action) GetRepoName(ctx context.Context) string { + a.loadRepo(ctx) return a.Repo.Name } // ShortRepoName returns the name of the action repository // trimmed to max 33 chars. -func (a *Action) ShortRepoName() string { - return base.EllipsisString(a.GetRepoName(), 33) +func (a *Action) ShortRepoName(ctx context.Context) string { + return base.EllipsisString(a.GetRepoName(ctx), 33) } // GetRepoPath returns the virtual path to the action repository. -func (a *Action) GetRepoPath() string { - return path.Join(a.GetRepoUserName(), a.GetRepoName()) +func (a *Action) GetRepoPath(ctx context.Context) string { + return path.Join(a.GetRepoUserName(ctx), a.GetRepoName(ctx)) } // ShortRepoPath returns the virtual path to the action repository // trimmed to max 20 + 1 + 33 chars. -func (a *Action) ShortRepoPath() string { - return path.Join(a.ShortRepoUserName(), a.ShortRepoName()) +func (a *Action) ShortRepoPath(ctx context.Context) string { + return path.Join(a.ShortRepoUserName(ctx), a.ShortRepoName(ctx)) } // GetRepoLink returns relative link to action repository. -func (a *Action) GetRepoLink() string { +func (a *Action) GetRepoLink(ctx context.Context) string { // path.Join will skip empty strings - return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName())) + return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName(ctx)), url.PathEscape(a.GetRepoName(ctx))) } // GetRepoAbsoluteLink returns the absolute link to action repository. -func (a *Action) GetRepoAbsoluteLink() string { - return setting.AppURL + url.PathEscape(a.GetRepoUserName()) + "/" + url.PathEscape(a.GetRepoName()) +func (a *Action) GetRepoAbsoluteLink(ctx context.Context) string { + return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx)) } // GetCommentHTMLURL returns link to action comment. -func (a *Action) GetCommentHTMLURL() string { - return a.getCommentHTMLURL(db.DefaultContext) +func (a *Action) GetCommentHTMLURL(ctx context.Context) string { + return a.getCommentHTMLURL(ctx) } func (a *Action) loadComment(ctx context.Context) (err error) { @@ -309,7 +309,7 @@ func (a *Action) getCommentHTMLURL(ctx context.Context) string { } _ = a.loadComment(ctx) if a.Comment != nil { - return a.Comment.HTMLURL() + return a.Comment.HTMLURL(ctx) } if len(a.GetIssueInfos()) == 0 { return "#" @@ -334,8 +334,8 @@ func (a *Action) getCommentHTMLURL(ctx context.Context) string { } // GetCommentLink returns link to action comment. -func (a *Action) GetCommentLink() string { - return a.getCommentLink(db.DefaultContext) +func (a *Action) GetCommentLink(ctx context.Context) string { + return a.getCommentLink(ctx) } func (a *Action) getCommentLink(ctx context.Context) string { @@ -344,7 +344,7 @@ func (a *Action) getCommentLink(ctx context.Context) string { } _ = a.loadComment(ctx) if a.Comment != nil { - return a.Comment.Link() + return a.Comment.Link(ctx) } if len(a.GetIssueInfos()) == 0 { return "#" @@ -374,8 +374,8 @@ func (a *Action) GetBranch() string { } // GetRefLink returns the action's ref link. -func (a *Action) GetRefLink() string { - return git.RefURL(a.GetRepoLink(), a.RefName) +func (a *Action) GetRefLink(ctx context.Context) string { + return git.RefURL(a.GetRepoLink(ctx), a.RefName) } // GetTag returns the action's repository tag. @@ -399,11 +399,10 @@ func (a *Action) GetIssueInfos() []string { return strings.SplitN(a.Content, "|", 3) } -// GetIssueTitle returns the title of first issue associated -// with the action. This function will be invoked in template so keep db.DefaultContext here -func (a *Action) GetIssueTitle() string { +// GetIssueTitle returns the title of first issue associated with the action. +func (a *Action) GetIssueTitle(ctx context.Context) string { index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64) - issue, err := issues_model.GetIssueByIndex(db.DefaultContext, a.RepoID, index) + issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index) if err != nil { log.Error("GetIssueByIndex: %v", err) return "500 when get issue" @@ -442,7 +441,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") } - cond, err := activityQueryCondition(opts) + cond, err := activityQueryCondition(ctx, opts) if err != nil { return nil, 0, err } @@ -473,11 +472,11 @@ func ActivityReadable(user, doer *user_model.User) bool { doer != nil && (doer.IsAdmin || user.ID == doer.ID) } -func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { +func activityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) { cond := builder.NewCond() if opts.RequestedTeam != nil && opts.RequestedUser == nil { - org, err := user_model.GetUserByID(db.DefaultContext, opts.RequestedTeam.OrgID) + org, err := user_model.GetUserByID(ctx, opts.RequestedTeam.OrgID) if err != nil { return nil, err } @@ -525,7 +524,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { } if opts.RequestedTeam != nil { - env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam) + env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(ctx, opts.RequestedTeam) teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos) if err != nil { return nil, fmt.Errorf("GetTeamRepositories: %w", err) @@ -564,12 +563,12 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { } // DeleteOldActions deletes all old actions from database. -func DeleteOldActions(olderThan time.Duration) (err error) { +func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error) { if olderThan <= 0 { return nil } - _, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{}) + _, err = db.GetEngine(ctx).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{}) return err } @@ -679,8 +678,8 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { } // NotifyWatchersActions creates batch of actions for every watcher. -func NotifyWatchersActions(acts []*Action) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func NotifyWatchersActions(ctx context.Context, acts []*Action) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/activities/action_test.go b/models/activities/action_test.go index 9a42740880..5467bd35fb 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -24,7 +24,7 @@ func TestAction_GetRepoPath(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) action := &activities_model.Action{RepoID: repo.ID} - assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath()) + assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath(db.DefaultContext)) } func TestAction_GetRepoLink(t *testing.T) { @@ -35,9 +35,9 @@ func TestAction_GetRepoLink(t *testing.T) { action := &activities_model.Action{RepoID: repo.ID, CommentID: comment.ID} setting.AppSubURL = "/suburl" expected := path.Join(setting.AppSubURL, owner.Name, repo.Name) - assert.Equal(t, expected, action.GetRepoLink()) - assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink()) - assert.Equal(t, comment.HTMLURL(), action.GetCommentHTMLURL()) + assert.Equal(t, expected, action.GetRepoLink(db.DefaultContext)) + assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink(db.DefaultContext)) + assert.Equal(t, comment.HTMLURL(db.DefaultContext), action.GetCommentHTMLURL(db.DefaultContext)) } func TestGetFeeds(t *testing.T) { diff --git a/models/activities/main_test.go b/models/activities/main_test.go index 6be54db658..43afb84ef1 100644 --- a/models/activities/main_test.go +++ b/models/activities/main_test.go @@ -4,7 +4,6 @@ package activities_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -14,7 +13,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/activities/notification.go b/models/activities/notification.go index ef263ef735..7c794564b6 100644 --- a/models/activities/notification.go +++ b/models/activities/notification.go @@ -175,8 +175,8 @@ func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_mo // CreateOrUpdateIssueNotifications creates an issue notification // for each watcher, or updates it if already exists // receiverID > 0 just send to receiver, else send to all watcher -func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID, receiverID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -435,21 +435,21 @@ func (n *Notification) loadUser(ctx context.Context) (err error) { } // GetRepo returns the repo of the notification -func (n *Notification) GetRepo() (*repo_model.Repository, error) { - return n.Repository, n.loadRepo(db.DefaultContext) +func (n *Notification) GetRepo(ctx context.Context) (*repo_model.Repository, error) { + return n.Repository, n.loadRepo(ctx) } // GetIssue returns the issue of the notification -func (n *Notification) GetIssue() (*issues_model.Issue, error) { - return n.Issue, n.loadIssue(db.DefaultContext) +func (n *Notification) GetIssue(ctx context.Context) (*issues_model.Issue, error) { + return n.Issue, n.loadIssue(ctx) } // HTMLURL formats a URL-string to the notification -func (n *Notification) HTMLURL() string { +func (n *Notification) HTMLURL(ctx context.Context) string { switch n.Source { case NotificationSourceIssue, NotificationSourcePullRequest: if n.Comment != nil { - return n.Comment.HTMLURL() + return n.Comment.HTMLURL(ctx) } return n.Issue.HTMLURL() case NotificationSourceCommit: @@ -461,11 +461,11 @@ func (n *Notification) HTMLURL() string { } // Link formats a relative URL-string to the notification -func (n *Notification) Link() string { +func (n *Notification) Link(ctx context.Context) string { switch n.Source { case NotificationSourceIssue, NotificationSourcePullRequest: if n.Comment != nil { - return n.Comment.Link() + return n.Comment.Link(ctx) } return n.Issue.Link() case NotificationSourceCommit: @@ -733,12 +733,12 @@ type UserIDCount struct { } // GetUIDsAndNotificationCounts between the two provided times -func GetUIDsAndNotificationCounts(since, until timeutil.TimeStamp) ([]UserIDCount, error) { +func GetUIDsAndNotificationCounts(ctx context.Context, since, until timeutil.TimeStamp) ([]UserIDCount, error) { sql := `SELECT user_id, count(*) AS count FROM notification ` + `WHERE user_id IN (SELECT user_id FROM notification WHERE updated_unix >= ? AND ` + `updated_unix < ?) AND status = ? GROUP BY user_id` var res []UserIDCount - return res, db.GetEngine(db.DefaultContext).SQL(sql, since, until, NotificationStatusUnread).Find(&res) + return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res) } // SetIssueReadBy sets issue to be read by given user. diff --git a/models/activities/notification_test.go b/models/activities/notification_test.go index 2d4c369a99..b90ce70536 100644 --- a/models/activities/notification_test.go +++ b/models/activities/notification_test.go @@ -20,7 +20,7 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(issue.ID, 0, 2, 0)) + assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(db.DefaultContext, issue.ID, 0, 2, 0)) // User 9 is inactive, thus notifications for user 1 and 4 are created notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{UserID: 1, IssueID: issue.ID}) @@ -50,7 +50,7 @@ func TestNotificationsForUser(t *testing.T) { func TestNotification_GetRepo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1}) - repo, err := notf.GetRepo() + repo, err := notf.GetRepo(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, repo, notf.Repository) assert.EqualValues(t, notf.RepoID, repo.ID) @@ -59,7 +59,7 @@ func TestNotification_GetRepo(t *testing.T) { func TestNotification_GetIssue(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1}) - issue, err := notf.GetIssue() + issue, err := notf.GetIssue(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, issue, notf.Issue) assert.EqualValues(t, notf.IssueID, issue.ID) diff --git a/models/activities/repo_activity.go b/models/activities/repo_activity.go index 509f9caaf3..91f5ac12bd 100644 --- a/models/activities/repo_activity.go +++ b/models/activities/repo_activity.go @@ -342,7 +342,7 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from // Published releases list sess := releasesForActivityStatement(ctx, repoID, fromTime) - sess.OrderBy("release.created_unix DESC") + sess.OrderBy("`release`.created_unix DESC") stats.PublishedReleases = make([]*repo_model.Release, 0) if err = sess.Find(&stats.PublishedReleases); err != nil { return err @@ -350,7 +350,7 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from // Published releases authors sess = releasesForActivityStatement(ctx, repoID, fromTime) - if _, err = sess.Select("count(distinct release.publisher_id) as `count`").Table("release").Get(&count); err != nil { + if _, err = sess.Select("count(distinct `release`.publisher_id) as `count`").Table("release").Get(&count); err != nil { return err } stats.PublishedReleaseAuthorCount = count @@ -359,7 +359,7 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from } func releasesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session { - return db.GetEngine(ctx).Where("release.repo_id = ?", repoID). - And("release.is_draft = ?", false). - And("release.created_unix >= ?", fromTime.Unix()) + return db.GetEngine(ctx).Where("`release`.repo_id = ?", repoID). + And("`release`.is_draft = ?", false). + And("`release`.created_unix >= ?", fromTime.Unix()) } diff --git a/models/activities/statistic.go b/models/activities/statistic.go index 9d379cd0c4..009c8c5ab4 100644 --- a/models/activities/statistic.go +++ b/models/activities/statistic.go @@ -4,6 +4,8 @@ package activities import ( + "context" + asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" @@ -47,12 +49,12 @@ type IssueByRepositoryCount struct { } // GetStatistic returns the database statistics -func GetStatistic() (stats Statistic) { - e := db.GetEngine(db.DefaultContext) - stats.Counter.User = user_model.CountUsers(nil) - stats.Counter.Org, _ = organization.CountOrgs(organization.FindOrgOptions{IncludePrivate: true}) +func GetStatistic(ctx context.Context) (stats Statistic) { + e := db.GetEngine(ctx) + stats.Counter.User = user_model.CountUsers(ctx, nil) + stats.Counter.Org, _ = organization.CountOrgs(ctx, organization.FindOrgOptions{IncludePrivate: true}) stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) - stats.Counter.Repo, _ = repo_model.CountRepositories(db.DefaultContext, repo_model.CountRepositoryOptions{}) + stats.Counter.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{}) stats.Counter.Watch, _ = e.Count(new(repo_model.Watch)) stats.Counter.Star, _ = e.Count(new(repo_model.Star)) stats.Counter.Access, _ = e.Count(new(access_model.Access)) @@ -100,7 +102,7 @@ func GetStatistic() (stats Statistic) { stats.Counter.Follow, _ = e.Count(new(user_model.Follow)) stats.Counter.Mirror, _ = e.Count(new(repo_model.Mirror)) stats.Counter.Release, _ = e.Count(new(repo_model.Release)) - stats.Counter.AuthSource = auth.CountSources() + stats.Counter.AuthSource = auth.CountSources(ctx) stats.Counter.Webhook, _ = e.Count(new(webhook.Webhook)) stats.Counter.Milestone, _ = e.Count(new(issues_model.Milestone)) stats.Counter.Label, _ = e.Count(new(issues_model.Label)) diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go index 3320799526..78fcd76d43 100644 --- a/models/activities/user_heatmap.go +++ b/models/activities/user_heatmap.go @@ -4,6 +4,8 @@ package activities import ( + "context" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -18,16 +20,16 @@ type UserHeatmapData struct { } // GetUserHeatmapDataByUser returns an array of UserHeatmapData -func GetUserHeatmapDataByUser(user, doer *user_model.User) ([]*UserHeatmapData, error) { - return getUserHeatmapData(user, nil, doer) +func GetUserHeatmapDataByUser(ctx context.Context, user, doer *user_model.User) ([]*UserHeatmapData, error) { + return getUserHeatmapData(ctx, user, nil, doer) } // GetUserHeatmapDataByUserTeam returns an array of UserHeatmapData -func GetUserHeatmapDataByUserTeam(user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { - return getUserHeatmapData(user, team, doer) +func GetUserHeatmapDataByUserTeam(ctx context.Context, user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { + return getUserHeatmapData(ctx, user, team, doer) } -func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { +func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { hdata := make([]*UserHeatmapData, 0) if !ActivityReadable(user, doer) { @@ -45,7 +47,7 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us groupByName = groupBy } - cond, err := activityQueryCondition(GetFeedsOptions{ + cond, err := activityQueryCondition(ctx, GetFeedsOptions{ RequestedUser: user, RequestedTeam: team, Actor: doer, @@ -60,7 +62,7 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us return nil, err } - return hdata, db.GetEngine(db.DefaultContext). + return hdata, db.GetEngine(ctx). Select(groupBy+" AS timestamp, count(user_id) as contributions"). Table("action"). Where(cond). diff --git a/models/activities/user_heatmap_test.go b/models/activities/user_heatmap_test.go index 98df7b38aa..657f0f043c 100644 --- a/models/activities/user_heatmap_test.go +++ b/models/activities/user_heatmap_test.go @@ -83,7 +83,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { assert.NoError(t, err) // Get the heatmap and compare - heatmap, err := activities_model.GetUserHeatmapDataByUser(user, doer) + heatmap, err := activities_model.GetUserHeatmapDataByUser(db.DefaultContext, user, doer) var contributions int for _, hm := range heatmap { contributions += int(hm.Contributions) diff --git a/models/admin/task.go b/models/admin/task.go index 8aa397ad35..c8bc95f981 100644 --- a/models/admin/task.go +++ b/models/admin/task.go @@ -48,11 +48,7 @@ type TranslatableMessage struct { } // LoadRepo loads repository of the task -func (task *Task) LoadRepo() error { - return task.loadRepo(db.DefaultContext) -} - -func (task *Task) loadRepo(ctx context.Context) error { +func (task *Task) LoadRepo(ctx context.Context) error { if task.Repo != nil { return nil } @@ -70,13 +66,13 @@ func (task *Task) loadRepo(ctx context.Context) error { } // LoadDoer loads do user -func (task *Task) LoadDoer() error { +func (task *Task) LoadDoer(ctx context.Context) error { if task.Doer != nil { return nil } var doer user_model.User - has, err := db.GetEngine(db.DefaultContext).ID(task.DoerID).Get(&doer) + has, err := db.GetEngine(ctx).ID(task.DoerID).Get(&doer) if err != nil { return err } else if !has { @@ -90,13 +86,13 @@ func (task *Task) LoadDoer() error { } // LoadOwner loads owner user -func (task *Task) LoadOwner() error { +func (task *Task) LoadOwner(ctx context.Context) error { if task.Owner != nil { return nil } var owner user_model.User - has, err := db.GetEngine(db.DefaultContext).ID(task.OwnerID).Get(&owner) + has, err := db.GetEngine(ctx).ID(task.OwnerID).Get(&owner) if err != nil { return err } else if !has { @@ -110,8 +106,8 @@ func (task *Task) LoadOwner() error { } // UpdateCols updates some columns -func (task *Task) UpdateCols(cols ...string) error { - _, err := db.GetEngine(db.DefaultContext).ID(task.ID).Cols(cols...).Update(task) +func (task *Task) UpdateCols(ctx context.Context, cols ...string) error { + _, err := db.GetEngine(ctx).ID(task.ID).Cols(cols...).Update(task) return err } @@ -169,12 +165,12 @@ func (err ErrTaskDoesNotExist) Unwrap() error { } // GetMigratingTask returns the migrating task by repo's id -func GetMigratingTask(repoID int64) (*Task, error) { +func GetMigratingTask(ctx context.Context, repoID int64) (*Task, error) { task := Task{ RepoID: repoID, Type: structs.TaskTypeMigrateRepo, } - has, err := db.GetEngine(db.DefaultContext).Get(&task) + has, err := db.GetEngine(ctx).Get(&task) if err != nil { return nil, err } else if !has { @@ -184,13 +180,13 @@ func GetMigratingTask(repoID int64) (*Task, error) { } // GetMigratingTaskByID returns the migrating task by repo's id -func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, error) { +func GetMigratingTaskByID(ctx context.Context, id, doerID int64) (*Task, *migration.MigrateOptions, error) { task := Task{ ID: id, DoerID: doerID, Type: structs.TaskTypeMigrateRepo, } - has, err := db.GetEngine(db.DefaultContext).Get(&task) + has, err := db.GetEngine(ctx).Get(&task) if err != nil { return nil, nil, err } else if !has { @@ -205,12 +201,12 @@ func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, e } // CreateTask creates a task on database -func CreateTask(task *Task) error { - return db.Insert(db.DefaultContext, task) +func CreateTask(ctx context.Context, task *Task) error { + return db.Insert(ctx, task) } // FinishMigrateTask updates database when migrate task finished -func FinishMigrateTask(task *Task) error { +func FinishMigrateTask(ctx context.Context, task *Task) error { task.Status = structs.TaskStatusFinished task.EndTime = timeutil.TimeStampNow() @@ -231,6 +227,6 @@ func FinishMigrateTask(task *Task) error { } task.PayloadContent = string(confBytes) - _, err = db.GetEngine(db.DefaultContext).ID(task.ID).Cols("status", "end_time", "payload_content").Update(task) + _, err = db.GetEngine(ctx).ID(task.ID).Cols("status", "end_time", "payload_content").Update(task) return err } diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index be019184eb..1da7c346de 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -88,14 +88,14 @@ func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([] } // CountUserGPGKeys return number of gpg keys a user own -func CountUserGPGKeys(userID int64) (int64, error) { - return db.GetEngine(db.DefaultContext).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) +func CountUserGPGKeys(ctx context.Context, userID int64) (int64, error) { + return db.GetEngine(ctx).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) } // GetGPGKeyByID returns public key by given ID. -func GetGPGKeyByID(keyID int64) (*GPGKey, error) { +func GetGPGKeyByID(ctx context.Context, keyID int64) (*GPGKey, error) { key := new(GPGKey) - has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key) + has, err := db.GetEngine(ctx).ID(keyID).Get(key) if err != nil { return nil, err } else if !has { @@ -105,14 +105,14 @@ func GetGPGKeyByID(keyID int64) (*GPGKey, error) { } // GetGPGKeysByKeyID returns public key by given ID. -func GetGPGKeysByKeyID(keyID string) ([]*GPGKey, error) { +func GetGPGKeysByKeyID(ctx context.Context, keyID string) ([]*GPGKey, error) { keys := make([]*GPGKey, 0, 1) - return keys, db.GetEngine(db.DefaultContext).Where("key_id=?", keyID).Find(&keys) + return keys, db.GetEngine(ctx).Where("key_id=?", keyID).Find(&keys) } // GPGKeyToEntity retrieve the imported key and the traducted entity -func GPGKeyToEntity(k *GPGKey) (*openpgp.Entity, error) { - impKey, err := GetGPGImportByKeyID(k.KeyID) +func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) { + impKey, err := GetGPGImportByKeyID(ctx, k.KeyID) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, e } // parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature) -func parseGPGKey(ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, error) { +func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, error) { pubkey := e.PrimaryKey expiry := getExpiryTime(e) @@ -159,7 +159,7 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, erro } // Check emails - userEmails, err := user_model.GetEmailAddresses(ownerID) + userEmails, err := user_model.GetEmailAddresses(ctx, ownerID) if err != nil { return nil, err } @@ -224,8 +224,8 @@ func deleteGPGKey(ctx context.Context, keyID string) (int64, error) { } // DeleteGPGKey deletes GPG key information in database. -func DeleteGPGKey(doer *user_model.User, id int64) (err error) { - key, err := GetGPGKeyByID(id) +func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err error) { + key, err := GetGPGKeyByID(ctx, id) if err != nil { if IsErrGPGKeyNotExist(err) { return nil @@ -238,7 +238,7 @@ func DeleteGPGKey(doer *user_model.User, id int64) (err error) { return ErrGPGKeyAccessDenied{doer.ID, key.ID} } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -251,7 +251,7 @@ func DeleteGPGKey(doer *user_model.User, id int64) (err error) { return committer.Commit() } -func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) { +func checkKeyEmails(ctx context.Context, email string, keys ...*GPGKey) (bool, string) { uid := int64(0) var userEmails []*user_model.EmailAddress var user *user_model.User @@ -263,10 +263,10 @@ func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) { } if key.Verified && key.OwnerID != 0 { if uid != key.OwnerID { - userEmails, _ = user_model.GetEmailAddresses(key.OwnerID) + userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID) uid = key.OwnerID user = &user_model.User{ID: uid} - _, _ = user_model.GetUser(user) + _, _ = user_model.GetUser(ctx, user) } for _, e := range userEmails { if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) { diff --git a/models/asymkey/gpg_key_add.go b/models/asymkey/gpg_key_add.go index eb4027b3a4..11124b1366 100644 --- a/models/asymkey/gpg_key_add.go +++ b/models/asymkey/gpg_key_add.go @@ -66,13 +66,13 @@ func addGPGSubKey(ctx context.Context, key *GPGKey) (err error) { } // AddGPGKey adds new public key to database. -func AddGPGKey(ownerID int64, content, token, signature string) ([]*GPGKey, error) { +func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature string) ([]*GPGKey, error) { ekeys, err := checkArmoredGPGKeyString(content) if err != nil { return nil, err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -153,7 +153,7 @@ func AddGPGKey(ownerID int64, content, token, signature string) ([]*GPGKey, erro // Get DB session - key, err := parseGPGKey(ownerID, ekey, verified) + key, err := parseGPGKey(ctx, ownerID, ekey, verified) if err != nil { return nil, err } diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index 65af0bc945..8ac4364404 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -125,7 +125,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific // If this a SSH signature handle it differently if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") { - return ParseCommitWithSSHSignature(c, committer) + return ParseCommitWithSSHSignature(ctx, c, committer) } // Parsing signature @@ -150,6 +150,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific // First check if the sig has a keyID and if so just look at that if commitVerification := hashAndVerifyForKeyID( + ctx, sig, c.Signature.Payload, committer, @@ -165,7 +166,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific // Now try to associate the signature with the committer, if present if committer.ID != 0 { - keys, err := ListGPGKeys(db.DefaultContext, committer.ID, db.ListOptions{}) + keys, err := ListGPGKeys(ctx, committer.ID, db.ListOptions{}) if err != nil { // Skipping failed to get gpg keys of user log.Error("ListGPGKeys: %v", err) return &CommitVerification{ @@ -175,7 +176,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific } } - committerEmailAddresses, _ := user_model.GetEmailAddresses(committer.ID) + committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID) activated := false for _, e := range committerEmailAddresses { if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { @@ -222,7 +223,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific } if err := gpgSettings.LoadPublicKeyContent(); err != nil { log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err) - } else if commitVerification := verifyWithGPGSettings(&gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { + } else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { if commitVerification.Reason == BadSignature { defaultReason = BadSignature } else { @@ -237,7 +238,7 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific } else if defaultGPGSettings == nil { log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String()) } else if defaultGPGSettings.Sign { - if commitVerification := verifyWithGPGSettings(defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { + if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil { if commitVerification.Reason == BadSignature { defaultReason = BadSignature } else { @@ -257,9 +258,9 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific } } -func verifyWithGPGSettings(gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *CommitVerification { +func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *CommitVerification { // First try to find the key in the db - if commitVerification := hashAndVerifyForKeyID(sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil { + if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil { return commitVerification } @@ -387,11 +388,11 @@ func hashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload s return nil } -func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *CommitVerification { +func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *CommitVerification { if keyID == "" { return nil } - keys, err := GetGPGKeysByKeyID(keyID) + keys, err := GetGPGKeysByKeyID(ctx, keyID) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ @@ -406,7 +407,7 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use for _, key := range keys { var primaryKeys []*GPGKey if key.PrimaryKeyID != "" { - primaryKeys, err = GetGPGKeysByKeyID(key.PrimaryKeyID) + primaryKeys, err = GetGPGKeysByKeyID(ctx, key.PrimaryKeyID) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ @@ -417,7 +418,7 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use } } - activated, email := checkKeyEmails(email, append([]*GPGKey{key}, primaryKeys...)...) + activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...) if !activated { continue } @@ -427,7 +428,7 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use Email: email, } if key.OwnerID != 0 { - owner, err := user_model.GetUserByID(db.DefaultContext, key.OwnerID) + owner, err := user_model.GetUserByID(ctx, key.OwnerID) if err == nil { signer = owner } else if !user_model.IsErrUserNotExist(err) { diff --git a/models/asymkey/gpg_key_import.go b/models/asymkey/gpg_key_import.go index 83881b5c22..c9d46d29e5 100644 --- a/models/asymkey/gpg_key_import.go +++ b/models/asymkey/gpg_key_import.go @@ -3,7 +3,11 @@ package asymkey -import "code.gitea.io/gitea/models/db" +import ( + "context" + + "code.gitea.io/gitea/models/db" +) // __________________ ________ ____ __. // / _____/\______ \/ _____/ | |/ _|____ ___.__. @@ -31,9 +35,9 @@ func init() { } // GetGPGImportByKeyID returns the import public armored key by given KeyID. -func GetGPGImportByKeyID(keyID string) (*GPGKeyImport, error) { +func GetGPGImportByKeyID(ctx context.Context, keyID string) (*GPGKeyImport, error) { key := new(GPGKeyImport) - has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key) + has, err := db.GetEngine(ctx).ID(keyID).Get(key) if err != nil { return nil, err } else if !has { diff --git a/models/asymkey/gpg_key_test.go b/models/asymkey/gpg_key_test.go index 6a0f9c6144..dee74bc281 100644 --- a/models/asymkey/gpg_key_test.go +++ b/models/asymkey/gpg_key_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" @@ -228,7 +229,7 @@ Q0KHb+QcycSgbDx0ZAvdIacuKvBBcbxrsmFUI4LR+oIup0G9gUc0roPvr014jYQL =zHo9 -----END PGP PUBLIC KEY BLOCK-----` - keys, err := AddGPGKey(1, testEmailWithUpperCaseLetters, "", "") + keys, err := AddGPGKey(db.DefaultContext, 1, testEmailWithUpperCaseLetters, "", "") assert.NoError(t, err) if assert.NotEmpty(t, keys) { key := keys[0] diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go index cb5eb7c853..be36482c74 100644 --- a/models/asymkey/gpg_key_verify.go +++ b/models/asymkey/gpg_key_verify.go @@ -4,6 +4,7 @@ package asymkey import ( + "context" "strconv" "time" @@ -29,8 +30,8 @@ import ( // This file provides functions relating verifying gpg keys // VerifyGPGKey marks a GPG key as verified -func VerifyGPGKey(ownerID int64, keyID, token, signature string) (string, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature string) (string, error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return "", err } diff --git a/models/asymkey/main_test.go b/models/asymkey/main_test.go index 701722be12..be71f848d9 100644 --- a/models/asymkey/main_test.go +++ b/models/asymkey/main_test.go @@ -4,7 +4,6 @@ package asymkey import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -12,7 +11,6 @@ import ( func TestMain(m *testing.M) { unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), FixtureFiles: []string{ "gpg_key.yml", "public_key.yml", diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index 8d84c2f7dc..f36738fb3d 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -91,7 +91,7 @@ func addKey(ctx context.Context, key *PublicKey) (err error) { } // AddPublicKey adds new public key to database and authorized_keys file. -func AddPublicKey(ownerID int64, name, content string, authSourceID int64) (*PublicKey, error) { +func AddPublicKey(ctx context.Context, ownerID int64, name, content string, authSourceID int64) (*PublicKey, error) { log.Trace(content) fingerprint, err := CalcFingerprint(content) @@ -99,7 +99,7 @@ func AddPublicKey(ownerID int64, name, content string, authSourceID int64) (*Pub return nil, err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -136,9 +136,9 @@ func AddPublicKey(ownerID int64, name, content string, authSourceID int64) (*Pub } // GetPublicKeyByID returns public key by given ID. -func GetPublicKeyByID(keyID int64) (*PublicKey, error) { +func GetPublicKeyByID(ctx context.Context, keyID int64) (*PublicKey, error) { key := new(PublicKey) - has, err := db.GetEngine(db.DefaultContext). + has, err := db.GetEngine(ctx). ID(keyID). Get(key) if err != nil { @@ -180,7 +180,7 @@ func SearchPublicKeyByContentExact(ctx context.Context, content string) (*Public } // SearchPublicKey returns a list of public keys matching the provided arguments. -func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) { +func SearchPublicKey(ctx context.Context, uid int64, fingerprint string) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) cond := builder.NewCond() if uid != 0 { @@ -189,12 +189,12 @@ func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) { if fingerprint != "" { cond = cond.And(builder.Eq{"fingerprint": fingerprint}) } - return keys, db.GetEngine(db.DefaultContext).Where(cond).Find(&keys) + return keys, db.GetEngine(ctx).Where(cond).Find(&keys) } // ListPublicKeys returns a list of public keys belongs to given user. -func ListPublicKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) { - sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal) +func ListPublicKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*PublicKey, error) { + sess := db.GetEngine(ctx).Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal) if listOptions.Page != 0 { sess = db.SetSessionPagination(sess, &listOptions) @@ -207,30 +207,30 @@ func ListPublicKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) } // CountPublicKeys count public keys a user has -func CountPublicKeys(userID int64) (int64, error) { - sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal) +func CountPublicKeys(ctx context.Context, userID int64) (int64, error) { + sess := db.GetEngine(ctx).Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal) return sess.Count(&PublicKey{}) } // ListPublicKeysBySource returns a list of synchronized public keys for a given user and login source. -func ListPublicKeysBySource(uid, authSourceID int64) ([]*PublicKey, error) { +func ListPublicKeysBySource(ctx context.Context, uid, authSourceID int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) - return keys, db.GetEngine(db.DefaultContext). + return keys, db.GetEngine(ctx). Where("owner_id = ? AND login_source_id = ?", uid, authSourceID). Find(&keys) } // UpdatePublicKeyUpdated updates public key use time. -func UpdatePublicKeyUpdated(id int64) error { +func UpdatePublicKeyUpdated(ctx context.Context, id int64) error { // Check if key exists before update as affected rows count is unreliable // and will return 0 affected rows if two updates are made at the same time - if cnt, err := db.GetEngine(db.DefaultContext).ID(id).Count(&PublicKey{}); err != nil { + if cnt, err := db.GetEngine(ctx).ID(id).Count(&PublicKey{}); err != nil { return err } else if cnt != 1 { return ErrKeyNotExist{id} } - _, err := db.GetEngine(db.DefaultContext).ID(id).Cols("updated_unix").Update(&PublicKey{ + _, err := db.GetEngine(ctx).ID(id).Cols("updated_unix").Update(&PublicKey{ UpdatedUnix: timeutil.TimeStampNow(), }) if err != nil { @@ -250,7 +250,7 @@ func DeletePublicKeys(ctx context.Context, keyIDs ...int64) error { } // PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key -func PublicKeysAreExternallyManaged(keys []*PublicKey) ([]bool, error) { +func PublicKeysAreExternallyManaged(ctx context.Context, keys []*PublicKey) ([]bool, error) { sources := make([]*auth.Source, 0, 5) externals := make([]bool, len(keys)) keyloop: @@ -272,7 +272,7 @@ keyloop: if source == nil { var err error - source, err = auth.GetSourceByID(key.LoginSourceID) + source, err = auth.GetSourceByID(ctx, key.LoginSourceID) if err != nil { if auth.IsErrSourceNotExist(err) { externals[i] = false @@ -295,15 +295,15 @@ keyloop: } // PublicKeyIsExternallyManaged returns whether the provided KeyID represents an externally managed Key -func PublicKeyIsExternallyManaged(id int64) (bool, error) { - key, err := GetPublicKeyByID(id) +func PublicKeyIsExternallyManaged(ctx context.Context, id int64) (bool, error) { + key, err := GetPublicKeyByID(ctx, id) if err != nil { return false, err } if key.LoginSourceID == 0 { return false, nil } - source, err := auth.GetSourceByID(key.LoginSourceID) + source, err := auth.GetSourceByID(ctx, key.LoginSourceID) if err != nil { if auth.IsErrSourceNotExist(err) { return false, nil @@ -318,9 +318,9 @@ func PublicKeyIsExternallyManaged(id int64) (bool, error) { } // deleteKeysMarkedForDeletion returns true if ssh keys needs update -func deleteKeysMarkedForDeletion(keys []string) (bool, error) { +func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) (bool, error) { // Start session - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return false, err } @@ -349,7 +349,7 @@ func deleteKeysMarkedForDeletion(keys []string) (bool, error) { } // AddPublicKeysBySource add a users public keys. Returns true if there are changes. -func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool { +func AddPublicKeysBySource(ctx context.Context, usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool { var sshKeysNeedUpdate bool for _, sshKey := range sshPublicKeys { var err error @@ -368,7 +368,7 @@ func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys [ marshalled = marshalled[:len(marshalled)-1] sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out)) - if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil { + if _, err := AddPublicKey(ctx, usr.ID, sshKeyName, marshalled, s.ID); err != nil { if IsErrKeyAlreadyExist(err) { log.Trace("AddPublicKeysBySource[%s]: Public SSH Key %s already exists for user", sshKeyName, usr.Name) } else { @@ -387,14 +387,14 @@ func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys [ } // SynchronizePublicKeys updates a users public keys. Returns true if there are changes. -func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool { +func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool { var sshKeysNeedUpdate bool log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name) // Get Public Keys from DB with current LDAP source var giteaKeys []string - keys, err := ListPublicKeysBySource(usr.ID, s.ID) + keys, err := ListPublicKeysBySource(ctx, usr.ID, s.ID) if err != nil { log.Error("synchronizePublicKeys[%s]: Error listing Public SSH Keys for user %s: %v", s.Name, usr.Name, err) } @@ -429,7 +429,7 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ newKeys = append(newKeys, key) } } - if AddPublicKeysBySource(usr, s, newKeys) { + if AddPublicKeysBySource(ctx, usr, s, newKeys) { sshKeysNeedUpdate = true } @@ -443,7 +443,7 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ } // Delete keys from DB that no longer exist in the source - needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete) + needUpd, err := deleteKeysMarkedForDeletion(ctx, giteaKeysToDelete) if err != nil { log.Error("synchronizePublicKeys[%s]: Error deleting Public Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) } diff --git a/models/asymkey/ssh_key_authorized_keys.go b/models/asymkey/ssh_key_authorized_keys.go index 77803d6709..267ab252c8 100644 --- a/models/asymkey/ssh_key_authorized_keys.go +++ b/models/asymkey/ssh_key_authorized_keys.go @@ -115,9 +115,9 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error { } // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again. -// Note: db.GetEngine(db.DefaultContext).Iterate does not get latest data after insert/delete, so we have to call this function +// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function // outside any session scope independently. -func RewriteAllPublicKeys() error { +func RewriteAllPublicKeys(ctx context.Context) error { // Don't rewrite key if internal server if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile { return nil @@ -165,7 +165,7 @@ func RewriteAllPublicKeys() error { } } - if err := RegeneratePublicKeys(db.DefaultContext, t); err != nil { + if err := RegeneratePublicKeys(ctx, t); err != nil { return err } diff --git a/models/asymkey/ssh_key_authorized_principals.go b/models/asymkey/ssh_key_authorized_principals.go index 592196c255..107d70c766 100644 --- a/models/asymkey/ssh_key_authorized_principals.go +++ b/models/asymkey/ssh_key_authorized_principals.go @@ -40,7 +40,7 @@ import ( const authorizedPrincipalsFile = "authorized_principals" // RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again. -// Note: db.GetEngine(db.DefaultContext).Iterate does not get latest data after insert/delete, so we have to call this function +// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function // outside any session scope independently. func RewriteAllPrincipalKeys(ctx context.Context) error { // Don't rewrite key if internal server diff --git a/models/asymkey/ssh_key_commit_verification.go b/models/asymkey/ssh_key_commit_verification.go index af73637c4a..a61f0663b1 100644 --- a/models/asymkey/ssh_key_commit_verification.go +++ b/models/asymkey/ssh_key_commit_verification.go @@ -5,6 +5,7 @@ package asymkey import ( "bytes" + "context" "fmt" "strings" @@ -17,10 +18,10 @@ import ( ) // ParseCommitWithSSHSignature check if signature is good against keystore. -func ParseCommitWithSSHSignature(c *git.Commit, committer *user_model.User) *CommitVerification { +func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification { // Now try to associate the signature with the committer, if present if committer.ID != 0 { - keys, err := ListPublicKeys(committer.ID, db.ListOptions{}) + keys, err := ListPublicKeys(ctx, committer.ID, db.ListOptions{}) if err != nil { // Skipping failed to get ssh keys of user log.Error("ListPublicKeys: %v", err) return &CommitVerification{ @@ -30,7 +31,7 @@ func ParseCommitWithSSHSignature(c *git.Commit, committer *user_model.User) *Com } } - committerEmailAddresses, err := user_model.GetEmailAddresses(committer.ID) + committerEmailAddresses, err := user_model.GetEmailAddresses(ctx, committer.ID) if err != nil { log.Error("GetEmailAddresses: %v", err) } diff --git a/models/asymkey/ssh_key_deploy.go b/models/asymkey/ssh_key_deploy.go index cc84392117..e347604bcd 100644 --- a/models/asymkey/ssh_key_deploy.go +++ b/models/asymkey/ssh_key_deploy.go @@ -48,8 +48,8 @@ func (key *DeployKey) AfterLoad() { } // GetContent gets associated public key content. -func (key *DeployKey) GetContent() error { - pkey, err := GetPublicKeyByID(key.KeyID) +func (key *DeployKey) GetContent(ctx context.Context) error { + pkey, err := GetPublicKeyByID(ctx, key.KeyID) if err != nil { return err } @@ -106,15 +106,15 @@ func addDeployKey(ctx context.Context, keyID, repoID int64, name, fingerprint st } // HasDeployKey returns true if public key is a deploy key of given repository. -func HasDeployKey(keyID, repoID int64) bool { - has, _ := db.GetEngine(db.DefaultContext). +func HasDeployKey(ctx context.Context, keyID, repoID int64) bool { + has, _ := db.GetEngine(ctx). Where("key_id = ? AND repo_id = ?", keyID, repoID). Get(new(DeployKey)) return has } // AddDeployKey add new deploy key to database and authorized_keys file. -func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey, error) { +func AddDeployKey(ctx context.Context, repoID int64, name, content string, readOnly bool) (*DeployKey, error) { fingerprint, err := CalcFingerprint(content) if err != nil { return nil, err @@ -125,7 +125,7 @@ func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey accessMode = perm.AccessModeWrite } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -197,8 +197,8 @@ func IsDeployKeyExistByKeyID(ctx context.Context, keyID int64) (bool, error) { } // UpdateDeployKeyCols updates deploy key information in the specified columns. -func UpdateDeployKeyCols(key *DeployKey, cols ...string) error { - _, err := db.GetEngine(db.DefaultContext).ID(key.ID).Cols(cols...).Update(key) +func UpdateDeployKeyCols(ctx context.Context, key *DeployKey, cols ...string) error { + _, err := db.GetEngine(ctx).ID(key.ID).Cols(cols...).Update(key) return err } @@ -240,6 +240,6 @@ func ListDeployKeys(ctx context.Context, opts *ListDeployKeysOptions) ([]*Deploy } // CountDeployKeys returns count deploy keys matching the provided arguments. -func CountDeployKeys(opts *ListDeployKeysOptions) (int64, error) { - return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&DeployKey{}) +func CountDeployKeys(ctx context.Context, opts *ListDeployKeysOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.toCond()).Count(&DeployKey{}) } diff --git a/models/asymkey/ssh_key_fingerprint.go b/models/asymkey/ssh_key_fingerprint.go index 8a8d4fce15..2d6af0e3d6 100644 --- a/models/asymkey/ssh_key_fingerprint.go +++ b/models/asymkey/ssh_key_fingerprint.go @@ -81,7 +81,7 @@ func CalcFingerprint(publicKeyContent string) (string, error) { fnName, fp string err error ) - if setting.SSH.StartBuiltinServer { + if len(setting.SSH.KeygenPath) == 0 { fnName = "calcFingerprintNative" fp, err = calcFingerprintNative(publicKeyContent) } else { diff --git a/models/asymkey/ssh_key_principals.go b/models/asymkey/ssh_key_principals.go index 6d43437ec1..92789e26f8 100644 --- a/models/asymkey/ssh_key_principals.go +++ b/models/asymkey/ssh_key_principals.go @@ -4,6 +4,7 @@ package asymkey import ( + "context" "fmt" "strings" @@ -24,15 +25,15 @@ import ( // This file contains functions related to principals // AddPrincipalKey adds new principal to database and authorized_principals file. -func AddPrincipalKey(ownerID int64, content string, authSourceID int64) (*PublicKey, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*PublicKey, error) { + dbCtx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } defer committer.Close() // Principals cannot be duplicated. - has, err := db.GetEngine(ctx). + has, err := db.GetEngine(dbCtx). Where("content = ? AND type = ?", content, KeyTypePrincipal). Get(new(PublicKey)) if err != nil { @@ -49,7 +50,7 @@ func AddPrincipalKey(ownerID int64, content string, authSourceID int64) (*Public Type: KeyTypePrincipal, LoginSourceID: authSourceID, } - if err = db.Insert(ctx, key); err != nil { + if err = db.Insert(dbCtx, key); err != nil { return nil, fmt.Errorf("addKey: %w", err) } @@ -59,11 +60,11 @@ func AddPrincipalKey(ownerID int64, content string, authSourceID int64) (*Public committer.Close() - return key, RewriteAllPrincipalKeys(db.DefaultContext) + return key, RewriteAllPrincipalKeys(ctx) } // CheckPrincipalKeyString strips spaces and returns an error if the given principal contains newlines -func CheckPrincipalKeyString(user *user_model.User, content string) (_ string, err error) { +func CheckPrincipalKeyString(ctx context.Context, user *user_model.User, content string) (_ string, err error) { if setting.SSH.Disabled { return "", db.ErrSSHDisabled{} } @@ -80,7 +81,7 @@ func CheckPrincipalKeyString(user *user_model.User, content string) (_ string, e case "anything": return content, nil case "email": - emails, err := user_model.GetEmailAddresses(user.ID) + emails, err := user_model.GetEmailAddresses(ctx, user.ID) if err != nil { return "", err } @@ -104,8 +105,8 @@ func CheckPrincipalKeyString(user *user_model.User, content string) (_ string, e } // ListPrincipalKeys returns a list of principals belongs to given user. -func ListPrincipalKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) { - sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal) +func ListPrincipalKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*PublicKey, error) { + sess := db.GetEngine(ctx).Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal) if listOptions.Page != 0 { sess = db.SetSessionPagination(sess, &listOptions) diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go index 5378ef66ba..d3e886b97f 100644 --- a/models/asymkey/ssh_key_test.go +++ b/models/asymkey/ssh_key_test.go @@ -51,7 +51,7 @@ func Test_SSHParsePublicKey(t *testing.T) { if err != nil { // Some servers do not support ecdsa format. if !strings.Contains(err.Error(), "line 1 too long:") { - assert.Fail(t, "%v", err) + assert.FailNow(t, "%v", err) } } assert.Equal(t, tc.keyType, keyTypeK) @@ -60,7 +60,7 @@ func Test_SSHParsePublicKey(t *testing.T) { t.Run("SSHParseKeyNative", func(t *testing.T) { keyTypeK, lengthK, err := SSHNativeParsePublicKey(tc.content) if err != nil { - assert.Fail(t, "%v", err) + assert.FailNow(t, "%v", err) } assert.Equal(t, tc.keyType, keyTypeK) assert.EqualValues(t, tc.length, lengthK) diff --git a/models/asymkey/ssh_key_verify.go b/models/asymkey/ssh_key_verify.go index d6c16eb467..e9f433248a 100644 --- a/models/asymkey/ssh_key_verify.go +++ b/models/asymkey/ssh_key_verify.go @@ -5,6 +5,7 @@ package asymkey import ( "bytes" + "context" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" @@ -13,8 +14,8 @@ import ( ) // VerifySSHKey marks a SSH key as verified -func VerifySSHKey(ownerID int64, fingerprint, token, signature string) (string, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signature string) (string, error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return "", err } diff --git a/models/auth/token.go b/models/auth/access_token.go similarity index 83% rename from models/auth/token.go rename to models/auth/access_token.go index fed03803d5..8abcc622bc 100644 --- a/models/auth/token.go +++ b/models/auth/access_token.go @@ -5,6 +5,7 @@ package auth import ( + "context" "crypto/subtle" "encoding/hex" "fmt" @@ -95,7 +96,7 @@ func init() { } // NewAccessToken creates new access token. -func NewAccessToken(t *AccessToken) error { +func NewAccessToken(ctx context.Context, t *AccessToken) error { salt, err := util.CryptoRandomString(10) if err != nil { return err @@ -108,7 +109,7 @@ func NewAccessToken(t *AccessToken) error { t.Token = hex.EncodeToString(token) t.TokenHash = HashToken(t.Token, t.TokenSalt) t.TokenLastEight = t.Token[len(t.Token)-8:] - _, err = db.GetEngine(db.DefaultContext).Insert(t) + _, err = db.GetEngine(ctx).Insert(t) return err } @@ -137,7 +138,7 @@ func getAccessTokenIDFromCache(token string) int64 { } // GetAccessTokenBySHA returns access token by given token value -func GetAccessTokenBySHA(token string) (*AccessToken, error) { +func GetAccessTokenBySHA(ctx context.Context, token string) (*AccessToken, error) { if token == "" { return nil, ErrAccessTokenEmpty{} } @@ -158,7 +159,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { TokenLastEight: lastEight, } // Re-get the token from the db in case it has been deleted in the intervening period - has, err := db.GetEngine(db.DefaultContext).ID(id).Get(accessToken) + has, err := db.GetEngine(ctx).ID(id).Get(accessToken) if err != nil { return nil, err } @@ -169,7 +170,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { } var tokens []AccessToken - err := db.GetEngine(db.DefaultContext).Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens) + err := db.GetEngine(ctx).Table(&AccessToken{}).Where("token_last_eight = ?", lastEight).Find(&tokens) if err != nil { return nil, err } else if len(tokens) == 0 { @@ -189,8 +190,8 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) { } // AccessTokenByNameExists checks if a token name has been used already by a user. -func AccessTokenByNameExists(token *AccessToken) (bool, error) { - return db.GetEngine(db.DefaultContext).Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist() +func AccessTokenByNameExists(ctx context.Context, token *AccessToken) (bool, error) { + return db.GetEngine(ctx).Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist() } // ListAccessTokensOptions contain filter options @@ -201,8 +202,8 @@ type ListAccessTokensOptions struct { } // ListAccessTokens returns a list of access tokens belongs to given user. -func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) { - sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID) +func ListAccessTokens(ctx context.Context, opts ListAccessTokensOptions) ([]*AccessToken, error) { + sess := db.GetEngine(ctx).Where("uid=?", opts.UserID) if len(opts.Name) != 0 { sess = sess.Where("name=?", opts.Name) @@ -222,14 +223,14 @@ func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) { } // UpdateAccessToken updates information of access token. -func UpdateAccessToken(t *AccessToken) error { - _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) +func UpdateAccessToken(ctx context.Context, t *AccessToken) error { + _, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t) return err } // CountAccessTokens count access tokens belongs to given user by options -func CountAccessTokens(opts ListAccessTokensOptions) (int64, error) { - sess := db.GetEngine(db.DefaultContext).Where("uid=?", opts.UserID) +func CountAccessTokens(ctx context.Context, opts ListAccessTokensOptions) (int64, error) { + sess := db.GetEngine(ctx).Where("uid=?", opts.UserID) if len(opts.Name) != 0 { sess = sess.Where("name=?", opts.Name) } @@ -237,8 +238,8 @@ func CountAccessTokens(opts ListAccessTokensOptions) (int64, error) { } // DeleteAccessTokenByID deletes access token by given ID. -func DeleteAccessTokenByID(id, userID int64) error { - cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&AccessToken{ +func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error { + cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{ UID: userID, }) if err != nil { diff --git a/models/auth/token_scope.go b/models/auth/access_token_scope.go similarity index 100% rename from models/auth/token_scope.go rename to models/auth/access_token_scope.go diff --git a/models/auth/token_scope_test.go b/models/auth/access_token_scope_test.go similarity index 100% rename from models/auth/token_scope_test.go rename to models/auth/access_token_scope_test.go diff --git a/models/auth/token_test.go b/models/auth/access_token_test.go similarity index 65% rename from models/auth/token_test.go rename to models/auth/access_token_test.go index 8a1e664950..72c937ffd6 100644 --- a/models/auth/token_test.go +++ b/models/auth/access_token_test.go @@ -7,6 +7,7 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -18,7 +19,7 @@ func TestNewAccessToken(t *testing.T) { UID: 3, Name: "Token C", } - assert.NoError(t, auth_model.NewAccessToken(token)) + assert.NoError(t, auth_model.NewAccessToken(db.DefaultContext, token)) unittest.AssertExistsAndLoadBean(t, token) invalidToken := &auth_model.AccessToken{ @@ -26,7 +27,7 @@ func TestNewAccessToken(t *testing.T) { UID: 2, Name: "Token F", } - assert.Error(t, auth_model.NewAccessToken(invalidToken)) + assert.Error(t, auth_model.NewAccessToken(db.DefaultContext, invalidToken)) } func TestAccessTokenByNameExists(t *testing.T) { @@ -39,16 +40,16 @@ func TestAccessTokenByNameExists(t *testing.T) { } // Check to make sure it doesn't exists already - exist, err := auth_model.AccessTokenByNameExists(token) + exist, err := auth_model.AccessTokenByNameExists(db.DefaultContext, token) assert.NoError(t, err) assert.False(t, exist) // Save it to the database - assert.NoError(t, auth_model.NewAccessToken(token)) + assert.NoError(t, auth_model.NewAccessToken(db.DefaultContext, token)) unittest.AssertExistsAndLoadBean(t, token) // This token must be found by name in the DB now - exist, err = auth_model.AccessTokenByNameExists(token) + exist, err = auth_model.AccessTokenByNameExists(db.DefaultContext, token) assert.NoError(t, err) assert.True(t, exist) @@ -59,32 +60,32 @@ func TestAccessTokenByNameExists(t *testing.T) { // Name matches but different user ID, this shouldn't exists in the // database - exist, err = auth_model.AccessTokenByNameExists(user4Token) + exist, err = auth_model.AccessTokenByNameExists(db.DefaultContext, user4Token) assert.NoError(t, err) assert.False(t, exist) } func TestGetAccessTokenBySHA(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - token, err := auth_model.GetAccessTokenBySHA("d2c6c1ba3890b309189a8e618c72a162e4efbf36") + token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "d2c6c1ba3890b309189a8e618c72a162e4efbf36") assert.NoError(t, err) assert.Equal(t, int64(1), token.UID) assert.Equal(t, "Token A", token.Name) assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash) assert.Equal(t, "e4efbf36", token.TokenLastEight) - _, err = auth_model.GetAccessTokenBySHA("notahash") + _, err = auth_model.GetAccessTokenBySHA(db.DefaultContext, "notahash") assert.Error(t, err) assert.True(t, auth_model.IsErrAccessTokenNotExist(err)) - _, err = auth_model.GetAccessTokenBySHA("") + _, err = auth_model.GetAccessTokenBySHA(db.DefaultContext, "") assert.Error(t, err) assert.True(t, auth_model.IsErrAccessTokenEmpty(err)) } func TestListAccessTokens(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - tokens, err := auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{UserID: 1}) + tokens, err := auth_model.ListAccessTokens(db.DefaultContext, auth_model.ListAccessTokensOptions{UserID: 1}) assert.NoError(t, err) if assert.Len(t, tokens, 2) { assert.Equal(t, int64(1), tokens[0].UID) @@ -93,39 +94,39 @@ func TestListAccessTokens(t *testing.T) { assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B") } - tokens, err = auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{UserID: 2}) + tokens, err = auth_model.ListAccessTokens(db.DefaultContext, auth_model.ListAccessTokensOptions{UserID: 2}) assert.NoError(t, err) if assert.Len(t, tokens, 1) { assert.Equal(t, int64(2), tokens[0].UID) assert.Equal(t, "Token A", tokens[0].Name) } - tokens, err = auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{UserID: 100}) + tokens, err = auth_model.ListAccessTokens(db.DefaultContext, auth_model.ListAccessTokensOptions{UserID: 100}) assert.NoError(t, err) assert.Empty(t, tokens) } func TestUpdateAccessToken(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - token, err := auth_model.GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c") + token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c") assert.NoError(t, err) token.Name = "Token Z" - assert.NoError(t, auth_model.UpdateAccessToken(token)) + assert.NoError(t, auth_model.UpdateAccessToken(db.DefaultContext, token)) unittest.AssertExistsAndLoadBean(t, token) } func TestDeleteAccessTokenByID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - token, err := auth_model.GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c") + token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c") assert.NoError(t, err) assert.Equal(t, int64(1), token.UID) - assert.NoError(t, auth_model.DeleteAccessTokenByID(token.ID, 1)) + assert.NoError(t, auth_model.DeleteAccessTokenByID(db.DefaultContext, token.ID, 1)) unittest.AssertNotExistsBean(t, token) - err = auth_model.DeleteAccessTokenByID(100, 100) + err = auth_model.DeleteAccessTokenByID(db.DefaultContext, 100, 100) assert.Error(t, err) assert.True(t, auth_model.IsErrAccessTokenNotExist(err)) } diff --git a/models/auth/auth_token.go b/models/auth/auth_token.go new file mode 100644 index 0000000000..65f1b169eb --- /dev/null +++ b/models/auth/auth_token.go @@ -0,0 +1,60 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package auth + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" +) + +var ErrAuthTokenNotExist = util.NewNotExistErrorf("auth token does not exist") + +type AuthToken struct { //nolint:revive + ID string `xorm:"pk"` + TokenHash string + UserID int64 `xorm:"INDEX"` + ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"` +} + +func init() { + db.RegisterModel(new(AuthToken)) +} + +func InsertAuthToken(ctx context.Context, t *AuthToken) error { + _, err := db.GetEngine(ctx).Insert(t) + return err +} + +func GetAuthTokenByID(ctx context.Context, id string) (*AuthToken, error) { + at := &AuthToken{} + + has, err := db.GetEngine(ctx).ID(id).Get(at) + if err != nil { + return nil, err + } + if !has { + return nil, ErrAuthTokenNotExist + } + return at, nil +} + +func UpdateAuthTokenByID(ctx context.Context, t *AuthToken) error { + _, err := db.GetEngine(ctx).ID(t.ID).Cols("token_hash", "expires_unix").Update(t) + return err +} + +func DeleteAuthTokenByID(ctx context.Context, id string) error { + _, err := db.GetEngine(ctx).ID(id).Delete(&AuthToken{}) + return err +} + +func DeleteExpiredAuthTokens(ctx context.Context) error { + _, err := db.GetEngine(ctx).Where(builder.Lt{"expires_unix": timeutil.TimeStampNow()}).Delete(&AuthToken{}) + return err +} diff --git a/models/auth/main_test.go b/models/auth/main_test.go index f8cbf3bd54..d772ea6b1c 100644 --- a/models/auth/main_test.go +++ b/models/auth/main_test.go @@ -4,7 +4,6 @@ package auth_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -17,7 +16,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 9c419eff69..d73ad6965d 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -66,6 +66,11 @@ func BuiltinApplications() map[string]*BuiltinOAuth2Application { DisplayName: "Git Credential Manager", RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, } + m["d57cb8c4-630c-4168-8324-ec79935e18d4"] = &BuiltinOAuth2Application{ + ConfigName: "tea", + DisplayName: "tea", + RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, + } return m } @@ -165,7 +170,7 @@ const lowerBase32Chars = "abcdefghijklmnopqrstuvwxyz234567" var base32Lower = base32.NewEncoding(lowerBase32Chars).WithPadding(base32.NoPadding) // GenerateClientSecret will generate the client secret and returns the plaintext and saves the hash at the database -func (app *OAuth2Application) GenerateClientSecret() (string, error) { +func (app *OAuth2Application) GenerateClientSecret(ctx context.Context) (string, error) { rBytes, err := util.CryptoRandomBytes(32) if err != nil { return "", err @@ -179,7 +184,7 @@ func (app *OAuth2Application) GenerateClientSecret() (string, error) { return "", err } app.ClientSecret = string(hashedSecret) - if _, err := db.GetEngine(db.DefaultContext).ID(app.ID).Cols("client_secret").Update(app); err != nil { + if _, err := db.GetEngine(ctx).ID(app.ID).Cols("client_secret").Update(app); err != nil { return "", err } return clientSecret, nil @@ -279,8 +284,8 @@ type UpdateOAuth2ApplicationOptions struct { } // UpdateOAuth2Application updates an oauth2 application -func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Application, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateOAuth2Application(ctx context.Context, opts UpdateOAuth2ApplicationOptions) (*OAuth2Application, error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -347,8 +352,8 @@ func deleteOAuth2Application(ctx context.Context, id, userid int64) error { } // DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app. -func DeleteOAuth2Application(id, userid int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteOAuth2Application(ctx context.Context, id, userid int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -368,8 +373,8 @@ func DeleteOAuth2Application(id, userid int64) error { } // ListOAuth2Applications returns a list of oauth2 applications belongs to given user. -func ListOAuth2Applications(uid int64, listOptions db.ListOptions) ([]*OAuth2Application, int64, error) { - sess := db.GetEngine(db.DefaultContext). +func ListOAuth2Applications(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*OAuth2Application, int64, error) { + sess := db.GetEngine(ctx). Where("uid=?", uid). Desc("id") @@ -627,18 +632,18 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error { } // GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources -func GetActiveOAuth2ProviderSources() ([]*Source, error) { +func GetActiveOAuth2ProviderSources(ctx context.Context) ([]*Source, error) { sources := make([]*Source, 0, 1) - if err := db.GetEngine(db.DefaultContext).Where("is_active = ? and type = ?", true, OAuth2).Find(&sources); err != nil { + if err := db.GetEngine(ctx).Where("is_active = ? and type = ?", true, OAuth2).Find(&sources); err != nil { return nil, err } return sources, nil } // GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name -func GetActiveOAuth2SourceByName(name string) (*Source, error) { +func GetActiveOAuth2SourceByName(ctx context.Context, name string) (*Source, error) { authSource := new(Source) - has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource) + has, err := db.GetEngine(ctx).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource) if err != nil { return nil, err } diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index b8f0bc12c6..122d43098c 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -18,7 +18,7 @@ import ( func TestOAuth2Application_GenerateClientSecret(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) - secret, err := app.GenerateClientSecret() + secret, err := app.GenerateClientSecret(db.DefaultContext) assert.NoError(t, err) assert.True(t, len(secret) > 0) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) @@ -28,7 +28,7 @@ func BenchmarkOAuth2Application_GenerateClientSecret(b *testing.B) { assert.NoError(b, unittest.PrepareTestDatabase()) app := unittest.AssertExistsAndLoadBean(b, &auth_model.OAuth2Application{ID: 1}) for i := 0; i < b.N; i++ { - _, _ = app.GenerateClientSecret() + _, _ = app.GenerateClientSecret(db.DefaultContext) } } @@ -78,7 +78,7 @@ func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) { func TestOAuth2Application_ValidateClientSecret(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) - secret, err := app.GenerateClientSecret() + secret, err := app.GenerateClientSecret(db.DefaultContext) assert.NoError(t, err) assert.True(t, app.ValidateClientSecret([]byte(secret))) assert.False(t, app.ValidateClientSecret([]byte("fewijfowejgfiowjeoifew"))) diff --git a/models/auth/session.go b/models/auth/session.go index b60e6a903b..28f25170ee 100644 --- a/models/auth/session.go +++ b/models/auth/session.go @@ -4,6 +4,7 @@ package auth import ( + "context" "fmt" "code.gitea.io/gitea/models/db" @@ -22,8 +23,8 @@ func init() { } // UpdateSession updates the session with provided id -func UpdateSession(key string, data []byte) error { - _, err := db.GetEngine(db.DefaultContext).ID(key).Update(&Session{ +func UpdateSession(ctx context.Context, key string, data []byte) error { + _, err := db.GetEngine(ctx).ID(key).Update(&Session{ Data: data, Expiry: timeutil.TimeStampNow(), }) @@ -31,12 +32,12 @@ func UpdateSession(key string, data []byte) error { } // ReadSession reads the data for the provided session -func ReadSession(key string) (*Session, error) { +func ReadSession(ctx context.Context, key string) (*Session, error) { session := Session{ Key: key, } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -55,24 +56,24 @@ func ReadSession(key string) (*Session, error) { } // ExistSession checks if a session exists -func ExistSession(key string) (bool, error) { +func ExistSession(ctx context.Context, key string) (bool, error) { session := Session{ Key: key, } - return db.GetEngine(db.DefaultContext).Get(&session) + return db.GetEngine(ctx).Get(&session) } // DestroySession destroys a session -func DestroySession(key string) error { - _, err := db.GetEngine(db.DefaultContext).Delete(&Session{ +func DestroySession(ctx context.Context, key string) error { + _, err := db.GetEngine(ctx).Delete(&Session{ Key: key, }) return err } // RegenerateSession regenerates a session from the old id -func RegenerateSession(oldKey, newKey string) (*Session, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -114,12 +115,12 @@ func RegenerateSession(oldKey, newKey string) (*Session, error) { } // CountSessions returns the number of sessions -func CountSessions() (int64, error) { - return db.GetEngine(db.DefaultContext).Count(&Session{}) +func CountSessions(ctx context.Context) (int64, error) { + return db.GetEngine(ctx).Count(&Session{}) } // CleanupSessions cleans up expired sessions -func CleanupSessions(maxLifetime int64) error { - _, err := db.GetEngine(db.DefaultContext).Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{}) +func CleanupSessions(ctx context.Context, maxLifetime int64) error { + _, err := db.GetEngine(ctx).Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{}) return err } diff --git a/models/auth/source.go b/models/auth/source.go index 0a904b7772..0f57d1702a 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -5,6 +5,7 @@ package auth import ( + "context" "fmt" "reflect" @@ -199,8 +200,8 @@ func (source *Source) SkipVerify() bool { // CreateSource inserts a AuthSource in the DB if not already // existing with the given name. -func CreateSource(source *Source) error { - has, err := db.GetEngine(db.DefaultContext).Where("name=?", source.Name).Exist(new(Source)) +func CreateSource(ctx context.Context, source *Source) error { + has, err := db.GetEngine(ctx).Where("name=?", source.Name).Exist(new(Source)) if err != nil { return err } else if has { @@ -211,7 +212,7 @@ func CreateSource(source *Source) error { source.IsSyncEnabled = false } - _, err = db.GetEngine(db.DefaultContext).Insert(source) + _, err = db.GetEngine(ctx).Insert(source) if err != nil { return err } @@ -232,7 +233,7 @@ func CreateSource(source *Source) error { err = registerableSource.RegisterSource() if err != nil { // remove the AuthSource in case of errors while registering configuration - if _, err := db.GetEngine(db.DefaultContext).Delete(source); err != nil { + if _, err := db.GetEngine(ctx).Delete(source); err != nil { log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err) } } @@ -240,33 +241,33 @@ func CreateSource(source *Source) error { } // Sources returns a slice of all login sources found in DB. -func Sources() ([]*Source, error) { +func Sources(ctx context.Context) ([]*Source, error) { auths := make([]*Source, 0, 6) - return auths, db.GetEngine(db.DefaultContext).Find(&auths) + return auths, db.GetEngine(ctx).Find(&auths) } // SourcesByType returns all sources of the specified type -func SourcesByType(loginType Type) ([]*Source, error) { +func SourcesByType(ctx context.Context, loginType Type) ([]*Source, error) { sources := make([]*Source, 0, 1) - if err := db.GetEngine(db.DefaultContext).Where("type = ?", loginType).Find(&sources); err != nil { + if err := db.GetEngine(ctx).Where("type = ?", loginType).Find(&sources); err != nil { return nil, err } return sources, nil } // AllActiveSources returns all active sources -func AllActiveSources() ([]*Source, error) { +func AllActiveSources(ctx context.Context) ([]*Source, error) { sources := make([]*Source, 0, 5) - if err := db.GetEngine(db.DefaultContext).Where("is_active = ?", true).Find(&sources); err != nil { + if err := db.GetEngine(ctx).Where("is_active = ?", true).Find(&sources); err != nil { return nil, err } return sources, nil } // ActiveSources returns all active sources of the specified type -func ActiveSources(tp Type) ([]*Source, error) { +func ActiveSources(ctx context.Context, tp Type) ([]*Source, error) { sources := make([]*Source, 0, 1) - if err := db.GetEngine(db.DefaultContext).Where("is_active = ? and type = ?", true, tp).Find(&sources); err != nil { + if err := db.GetEngine(ctx).Where("is_active = ? and type = ?", true, tp).Find(&sources); err != nil { return nil, err } return sources, nil @@ -274,11 +275,11 @@ func ActiveSources(tp Type) ([]*Source, error) { // IsSSPIEnabled returns true if there is at least one activated login // source of type LoginSSPI -func IsSSPIEnabled() bool { +func IsSSPIEnabled(ctx context.Context) bool { if !db.HasEngine { return false } - sources, err := ActiveSources(SSPI) + sources, err := ActiveSources(ctx, SSPI) if err != nil { log.Error("ActiveSources: %v", err) return false @@ -287,7 +288,7 @@ func IsSSPIEnabled() bool { } // GetSourceByID returns login source by given ID. -func GetSourceByID(id int64) (*Source, error) { +func GetSourceByID(ctx context.Context, id int64) (*Source, error) { source := new(Source) if id == 0 { source.Cfg = registeredConfigs[NoType]() @@ -297,7 +298,7 @@ func GetSourceByID(id int64) (*Source, error) { return source, nil } - has, err := db.GetEngine(db.DefaultContext).ID(id).Get(source) + has, err := db.GetEngine(ctx).ID(id).Get(source) if err != nil { return nil, err } else if !has { @@ -307,24 +308,24 @@ func GetSourceByID(id int64) (*Source, error) { } // UpdateSource updates a Source record in DB. -func UpdateSource(source *Source) error { +func UpdateSource(ctx context.Context, source *Source) error { var originalSource *Source if source.IsOAuth2() { // keep track of the original values so we can restore in case of errors while registering OAuth2 providers var err error - if originalSource, err = GetSourceByID(source.ID); err != nil { + if originalSource, err = GetSourceByID(ctx, source.ID); err != nil { return err } } - has, err := db.GetEngine(db.DefaultContext).Where("name=? AND id!=?", source.Name, source.ID).Exist(new(Source)) + has, err := db.GetEngine(ctx).Where("name=? AND id!=?", source.Name, source.ID).Exist(new(Source)) if err != nil { return err } else if has { return ErrSourceAlreadyExist{source.Name} } - _, err = db.GetEngine(db.DefaultContext).ID(source.ID).AllCols().Update(source) + _, err = db.GetEngine(ctx).ID(source.ID).AllCols().Update(source) if err != nil { return err } @@ -345,7 +346,7 @@ func UpdateSource(source *Source) error { err = registerableSource.RegisterSource() if err != nil { // restore original values since we cannot update the provider it self - if _, err := db.GetEngine(db.DefaultContext).ID(source.ID).AllCols().Update(originalSource); err != nil { + if _, err := db.GetEngine(ctx).ID(source.ID).AllCols().Update(originalSource); err != nil { log.Error("UpdateSource: Error while wrapOpenIDConnectInitializeError: %v", err) } } @@ -353,8 +354,8 @@ func UpdateSource(source *Source) error { } // CountSources returns number of login sources. -func CountSources() int64 { - count, _ := db.GetEngine(db.DefaultContext).Count(new(Source)) +func CountSources(ctx context.Context) int64 { + count, _ := db.GetEngine(ctx).Count(new(Source)) return count } diff --git a/models/auth/source_test.go b/models/auth/source_test.go index 31216cca8e..36e76d5e28 100644 --- a/models/auth/source_test.go +++ b/models/auth/source_test.go @@ -42,7 +42,7 @@ func TestDumpAuthSource(t *testing.T) { auth_model.RegisterTypeConfig(auth_model.OAuth2, new(TestSource)) - auth_model.CreateSource(&auth_model.Source{ + auth_model.CreateSource(db.DefaultContext, &auth_model.Source{ Type: auth_model.OAuth2, Name: "TestSource", IsActive: false, diff --git a/models/auth/twofactor.go b/models/auth/twofactor.go index 751a281f7e..51061e5205 100644 --- a/models/auth/twofactor.go +++ b/models/auth/twofactor.go @@ -4,6 +4,7 @@ package auth import ( + "context" "crypto/md5" "crypto/subtle" "encoding/base32" @@ -121,22 +122,22 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { } // NewTwoFactor creates a new two-factor authentication token. -func NewTwoFactor(t *TwoFactor) error { - _, err := db.GetEngine(db.DefaultContext).Insert(t) +func NewTwoFactor(ctx context.Context, t *TwoFactor) error { + _, err := db.GetEngine(ctx).Insert(t) return err } // UpdateTwoFactor updates a two-factor authentication token. -func UpdateTwoFactor(t *TwoFactor) error { - _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) +func UpdateTwoFactor(ctx context.Context, t *TwoFactor) error { + _, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t) return err } // GetTwoFactorByUID returns the two-factor authentication token associated with // the user, if any. -func GetTwoFactorByUID(uid int64) (*TwoFactor, error) { +func GetTwoFactorByUID(ctx context.Context, uid int64) (*TwoFactor, error) { twofa := &TwoFactor{} - has, err := db.GetEngine(db.DefaultContext).Where("uid=?", uid).Get(twofa) + has, err := db.GetEngine(ctx).Where("uid=?", uid).Get(twofa) if err != nil { return nil, err } else if !has { @@ -147,13 +148,13 @@ func GetTwoFactorByUID(uid int64) (*TwoFactor, error) { // HasTwoFactorByUID returns the two-factor authentication token associated with // the user, if any. -func HasTwoFactorByUID(uid int64) (bool, error) { - return db.GetEngine(db.DefaultContext).Where("uid=?", uid).Exist(&TwoFactor{}) +func HasTwoFactorByUID(ctx context.Context, uid int64) (bool, error) { + return db.GetEngine(ctx).Where("uid=?", uid).Exist(&TwoFactor{}) } // DeleteTwoFactorByID deletes two-factor authentication token by given ID. -func DeleteTwoFactorByID(id, userID int64) error { - cnt, err := db.GetEngine(db.DefaultContext).ID(id).Delete(&TwoFactor{ +func DeleteTwoFactorByID(ctx context.Context, id, userID int64) error { + cnt, err := db.GetEngine(ctx).ID(id).Delete(&TwoFactor{ UID: userID, }) if err != nil { diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index db5dd7eea5..d12713bd37 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -67,11 +67,7 @@ func (cred WebAuthnCredential) TableName() string { } // UpdateSignCount will update the database value of SignCount -func (cred *WebAuthnCredential) UpdateSignCount() error { - return cred.updateSignCount(db.DefaultContext) -} - -func (cred *WebAuthnCredential) updateSignCount(ctx context.Context) error { +func (cred *WebAuthnCredential) UpdateSignCount(ctx context.Context) error { _, err := db.GetEngine(ctx).ID(cred.ID).Cols("sign_count").Update(cred) return err } @@ -113,30 +109,18 @@ func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { } // GetWebAuthnCredentialsByUID returns all WebAuthn credentials of the given user -func GetWebAuthnCredentialsByUID(uid int64) (WebAuthnCredentialList, error) { - return getWebAuthnCredentialsByUID(db.DefaultContext, uid) -} - -func getWebAuthnCredentialsByUID(ctx context.Context, uid int64) (WebAuthnCredentialList, error) { +func GetWebAuthnCredentialsByUID(ctx context.Context, uid int64) (WebAuthnCredentialList, error) { creds := make(WebAuthnCredentialList, 0) return creds, db.GetEngine(ctx).Where("user_id = ?", uid).Find(&creds) } // ExistsWebAuthnCredentialsForUID returns if the given user has credentials -func ExistsWebAuthnCredentialsForUID(uid int64) (bool, error) { - return existsWebAuthnCredentialsByUID(db.DefaultContext, uid) -} - -func existsWebAuthnCredentialsByUID(ctx context.Context, uid int64) (bool, error) { +func ExistsWebAuthnCredentialsForUID(ctx context.Context, uid int64) (bool, error) { return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{}) } // GetWebAuthnCredentialByName returns WebAuthn credential by id -func GetWebAuthnCredentialByName(uid int64, name string) (*WebAuthnCredential, error) { - return getWebAuthnCredentialByName(db.DefaultContext, uid, name) -} - -func getWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*WebAuthnCredential, error) { +func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*WebAuthnCredential, error) { cred := new(WebAuthnCredential) if found, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ?", uid, strings.ToLower(name)).Get(cred); err != nil { return nil, err @@ -147,11 +131,7 @@ func getWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (* } // GetWebAuthnCredentialByID returns WebAuthn credential by id -func GetWebAuthnCredentialByID(id int64) (*WebAuthnCredential, error) { - return getWebAuthnCredentialByID(db.DefaultContext, id) -} - -func getWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) { +func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) { cred := new(WebAuthnCredential) if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil { return nil, err @@ -162,16 +142,12 @@ func getWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredenti } // HasWebAuthnRegistrationsByUID returns whether a given user has WebAuthn registrations -func HasWebAuthnRegistrationsByUID(uid int64) (bool, error) { - return db.GetEngine(db.DefaultContext).Where("user_id = ?", uid).Exist(&WebAuthnCredential{}) +func HasWebAuthnRegistrationsByUID(ctx context.Context, uid int64) (bool, error) { + return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{}) } // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID -func GetWebAuthnCredentialByCredID(userID int64, credID []byte) (*WebAuthnCredential, error) { - return getWebAuthnCredentialByCredID(db.DefaultContext, userID, credID) -} - -func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) { +func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) { cred := new(WebAuthnCredential) if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil { return nil, err @@ -182,11 +158,7 @@ func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []b } // CreateCredential will create a new WebAuthnCredential from the given Credential -func CreateCredential(userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) { - return createCredential(db.DefaultContext, userID, name, cred) -} - -func createCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) { +func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) { c := &WebAuthnCredential{ UserID: userID, Name: name, @@ -205,18 +177,14 @@ func createCredential(ctx context.Context, userID int64, name string, cred *weba } // DeleteCredential will delete WebAuthnCredential -func DeleteCredential(id, userID int64) (bool, error) { - return deleteCredential(db.DefaultContext, id, userID) -} - -func deleteCredential(ctx context.Context, id, userID int64) (bool, error) { +func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) { had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{}) return had > 0, err } // WebAuthnCredentials implementns the webauthn.User interface -func WebAuthnCredentials(userID int64) ([]webauthn.Credential, error) { - dbCreds, err := GetWebAuthnCredentialsByUID(userID) +func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) { + dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID) if err != nil { return nil, err } diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go index 6f2ec087c7..f1cf398adf 100644 --- a/models/auth/webauthn_test.go +++ b/models/auth/webauthn_test.go @@ -7,6 +7,7 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "github.com/go-webauthn/webauthn/webauthn" @@ -16,11 +17,11 @@ import ( func TestGetWebAuthnCredentialByID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - res, err := auth_model.GetWebAuthnCredentialByID(1) + res, err := auth_model.GetWebAuthnCredentialByID(db.DefaultContext, 1) assert.NoError(t, err) assert.Equal(t, "WebAuthn credential", res.Name) - _, err = auth_model.GetWebAuthnCredentialByID(342432) + _, err = auth_model.GetWebAuthnCredentialByID(db.DefaultContext, 342432) assert.Error(t, err) assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err)) } @@ -28,7 +29,7 @@ func TestGetWebAuthnCredentialByID(t *testing.T) { func TestGetWebAuthnCredentialsByUID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - res, err := auth_model.GetWebAuthnCredentialsByUID(32) + res, err := auth_model.GetWebAuthnCredentialsByUID(db.DefaultContext, 32) assert.NoError(t, err) assert.Len(t, res, 1) assert.Equal(t, "WebAuthn credential", res[0].Name) @@ -42,7 +43,7 @@ func TestWebAuthnCredential_UpdateSignCount(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1}) cred.SignCount = 1 - assert.NoError(t, cred.UpdateSignCount()) + assert.NoError(t, cred.UpdateSignCount(db.DefaultContext)) unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{ID: 1, SignCount: 1}) } @@ -50,14 +51,14 @@ func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1}) cred.SignCount = 0xffffffff - assert.NoError(t, cred.UpdateSignCount()) + assert.NoError(t, cred.UpdateSignCount(db.DefaultContext)) unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{ID: 1, SignCount: 0xffffffff}) } func TestCreateCredential(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - res, err := auth_model.CreateCredential(1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")}) + res, err := auth_model.CreateCredential(db.DefaultContext, 1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")}) assert.NoError(t, err) assert.Equal(t, "WebAuthn Created Credential", res.Name) assert.Equal(t, []byte("Test"), res.CredentialID) diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go index e40aa3f542..c197a22dc1 100644 --- a/models/avatars/avatar.go +++ b/models/avatars/avatar.go @@ -5,18 +5,20 @@ package avatars import ( "context" + "fmt" "net/url" "path" "strconv" "strings" - "sync" + "sync/atomic" "code.gitea.io/gitea/models/db" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + + "strk.kbt.io/projects/go/libravatar" ) const ( @@ -36,24 +38,54 @@ func init() { db.RegisterModel(new(EmailHash)) } -var ( +type avatarSettingStruct struct { defaultAvatarLink string - once sync.Once -) + gravatarSource string + gravatarSourceURL *url.URL + libravatar *libravatar.Libravatar +} -// DefaultAvatarLink the default avatar link -func DefaultAvatarLink() string { - once.Do(func() { +var avatarSettingAtomic atomic.Pointer[avatarSettingStruct] + +func loadAvatarSetting() (*avatarSettingStruct, error) { + s := avatarSettingAtomic.Load() + if s == nil || s.gravatarSource != setting.GravatarSource { + s = &avatarSettingStruct{} u, err := url.Parse(setting.AppSubURL) if err != nil { - log.Error("Can not parse AppSubURL: %v", err) - return + return nil, fmt.Errorf("unable to parse AppSubURL: %w", err) } u.Path = path.Join(u.Path, "/assets/img/avatar_default.png") - defaultAvatarLink = u.String() - }) - return defaultAvatarLink + s.defaultAvatarLink = u.String() + + s.gravatarSourceURL, err = url.Parse(setting.GravatarSource) + if err != nil { + return nil, fmt.Errorf("unable to parse GravatarSource %q: %w", setting.GravatarSource, err) + } + + s.libravatar = libravatar.New() + if s.gravatarSourceURL.Scheme == "https" { + s.libravatar.SetUseHTTPS(true) + s.libravatar.SetSecureFallbackHost(s.gravatarSourceURL.Host) + } else { + s.libravatar.SetUseHTTPS(false) + s.libravatar.SetFallbackHost(s.gravatarSourceURL.Host) + } + + avatarSettingAtomic.Store(s) + } + return s, nil +} + +// DefaultAvatarLink the default avatar link +func DefaultAvatarLink() string { + a, err := loadAvatarSetting() + if err != nil { + log.Error("Failed to loadAvatarSetting: %v", err) + return "" + } + return a.defaultAvatarLink } // HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/ @@ -62,13 +94,13 @@ func HashEmail(email string) string { } // GetEmailForHash converts a provided md5sum to the email -func GetEmailForHash(md5Sum string) (string, error) { +func GetEmailForHash(ctx context.Context, md5Sum string) (string, error) { return cache.GetString("Avatar:"+md5Sum, func() (string, error) { emailHash := EmailHash{ Hash: strings.ToLower(strings.TrimSpace(md5Sum)), } - _, err := db.GetEngine(db.DefaultContext).Get(&emailHash) + _, err := db.GetEngine(ctx).Get(&emailHash) return emailHash.Email, err }) } @@ -76,7 +108,11 @@ func GetEmailForHash(md5Sum string) (string, error) { // LibravatarURL returns the URL for the given email. Slow due to the DNS lookup. // This function should only be called if a federated avatar service is enabled. func LibravatarURL(email string) (*url.URL, error) { - urlStr, err := system_model.LibravatarService.FromEmail(email) + a, err := loadAvatarSetting() + if err != nil { + return nil, err + } + urlStr, err := a.libravatar.FromEmail(email) if err != nil { log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err) return nil, err @@ -91,7 +127,7 @@ func LibravatarURL(email string) (*url.URL, error) { // saveEmailHash returns an avatar link for a provided email, // the email and hash are saved into database, which will be used by GetEmailForHash later -func saveEmailHash(email string) string { +func saveEmailHash(ctx context.Context, email string) string { lowerEmail := strings.ToLower(strings.TrimSpace(email)) emailHash := HashEmail(lowerEmail) _, _ = cache.GetString("Avatar:"+emailHash, func() (string, error) { @@ -100,7 +136,7 @@ func saveEmailHash(email string) string { Hash: emailHash, } // OK we're going to open a session just because I think that that might hide away any problems with postgres reporting errors - if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := db.WithTx(ctx, func(ctx context.Context) error { has, err := db.GetEngine(ctx).Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash)) if has || err != nil { // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time @@ -153,16 +189,14 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final return DefaultAvatarLink() } - disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar, - setting.GetDefaultDisableGravatar(), - ) + avatarSetting, err := loadAvatarSetting() + if err != nil { + return DefaultAvatarLink() + } - enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar, - setting.GetDefaultEnableFederatedAvatar(disableGravatar)) - - var err error - if enableFederatedAvatar && system_model.LibravatarService != nil { - emailHash := saveEmailHash(email) + enableFederatedAvatar := setting.Config().Picture.EnableFederatedAvatar.Value(ctx) + if enableFederatedAvatar { + emailHash := saveEmailHash(ctx, email) if final { // for final link, we can spend more time on slow external query var avatarURL *url.URL @@ -179,9 +213,10 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final return urlStr } + disableGravatar := setting.Config().Picture.DisableGravatar.Value(ctx) if !disableGravatar { // copy GravatarSourceURL, because we will modify its Path. - avatarURLCopy := *system_model.GravatarSourceURL + avatarURLCopy := *avatarSetting.gravatarSourceURL avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email)) return generateRecognizedAvatarURL(avatarURLCopy, size) } diff --git a/models/avatars/avatar_test.go b/models/avatars/avatar_test.go index 59daaeb669..c8f7a6574b 100644 --- a/models/avatars/avatar_test.go +++ b/models/avatars/avatar_test.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/setting/config" "github.com/stretchr/testify/assert" ) @@ -17,19 +18,16 @@ import ( const gravatarSource = "https://secure.gravatar.com/avatar/" func disableGravatar(t *testing.T) { - err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureEnableFederatedAvatar, "false") + err := system_model.SetSettings(db.DefaultContext, map[string]string{setting.Config().Picture.EnableFederatedAvatar.DynKey(): "false"}) assert.NoError(t, err) - err = system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "true") + err = system_model.SetSettings(db.DefaultContext, map[string]string{setting.Config().Picture.DisableGravatar.DynKey(): "true"}) assert.NoError(t, err) - system_model.LibravatarService = nil } func enableGravatar(t *testing.T) { - err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false") + err := system_model.SetSettings(db.DefaultContext, map[string]string{setting.Config().Picture.DisableGravatar.DynKey(): "false"}) assert.NoError(t, err) setting.GravatarSource = gravatarSource - err = system_model.Init(db.DefaultContext) - assert.NoError(t, err) } func TestHashEmail(t *testing.T) { @@ -47,10 +45,12 @@ func TestSizedAvatarLink(t *testing.T) { setting.AppSubURL = "/testsuburl" disableGravatar(t) + config.GetDynGetter().InvalidateCache() assert.Equal(t, "/testsuburl/assets/img/avatar_default.png", avatars_model.GenerateEmailAvatarFastLink(db.DefaultContext, "gitea@example.com", 100)) enableGravatar(t) + config.GetDynGetter().InvalidateCache() assert.Equal(t, "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100", avatars_model.GenerateEmailAvatarFastLink(db.DefaultContext, "gitea@example.com", 100), diff --git a/models/avatars/main_test.go b/models/avatars/main_test.go index 6507632717..c721a7dc2a 100644 --- a/models/avatars/main_test.go +++ b/models/avatars/main_test.go @@ -4,7 +4,6 @@ package avatars_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -15,7 +14,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/db/main_test.go b/models/db/main_test.go index ce48502918..7d80b400fe 100644 --- a/models/db/main_test.go +++ b/models/db/main_test.go @@ -4,7 +4,6 @@ package db_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -14,7 +13,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/db/paginator/main_test.go b/models/db/paginator/main_test.go index b0557bc5a4..47993aed6b 100644 --- a/models/db/paginator/main_test.go +++ b/models/db/paginator/main_test.go @@ -4,14 +4,11 @@ package paginator import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", "..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/dbfs/main_test.go b/models/dbfs/main_test.go index 62db3592be..537ba0935d 100644 --- a/models/dbfs/main_test.go +++ b/models/dbfs/main_test.go @@ -4,14 +4,11 @@ package dbfs import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/fixture_generation.go b/models/fixture_generation.go index abf880ee8e..6234caefad 100644 --- a/models/fixture_generation.go +++ b/models/fixture_generation.go @@ -4,6 +4,7 @@ package models import ( + "context" "fmt" "strings" @@ -14,15 +15,15 @@ import ( // GetYamlFixturesAccess returns a string containing the contents // for the access table, as recalculated using repo.RecalculateAccesses() -func GetYamlFixturesAccess() (string, error) { +func GetYamlFixturesAccess(ctx context.Context) (string, error) { repos := make([]*repo_model.Repository, 0, 50) - if err := db.GetEngine(db.DefaultContext).Find(&repos); err != nil { + if err := db.GetEngine(ctx).Find(&repos); err != nil { return "", err } for _, repo := range repos { - repo.MustOwner(db.DefaultContext) - if err := access_model.RecalculateAccesses(db.DefaultContext, repo); err != nil { + repo.MustOwner(ctx) + if err := access_model.RecalculateAccesses(ctx, repo); err != nil { return "", err } } @@ -30,7 +31,7 @@ func GetYamlFixturesAccess() (string, error) { var b strings.Builder accesses := make([]*access_model.Access, 0, 200) - if err := db.GetEngine(db.DefaultContext).OrderBy("user_id, repo_id").Find(&accesses); err != nil { + if err := db.GetEngine(ctx).OrderBy("user_id, repo_id").Find(&accesses); err != nil { return "", err } @@ -40,7 +41,9 @@ func GetYamlFixturesAccess() (string, error) { fmt.Fprintf(&b, " user_id: %d\n", a.UserID) fmt.Fprintf(&b, " repo_id: %d\n", a.RepoID) fmt.Fprintf(&b, " mode: %d\n", a.Mode) - fmt.Fprintf(&b, "\n") + if i < len(accesses)-1 { + fmt.Fprintf(&b, "\n") + } } return b.String(), nil diff --git a/models/fixture_test.go b/models/fixture_test.go index 8a28db8164..de5f412388 100644 --- a/models/fixture_test.go +++ b/models/fixture_test.go @@ -4,10 +4,12 @@ package models import ( + "context" "os" "path/filepath" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/util" @@ -17,18 +19,19 @@ import ( func TestFixtureGeneration(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(gen func() (string, error), name string) { - expected, err := gen() + test := func(ctx context.Context, gen func(ctx context.Context) (string, error), name string) { + expected, err := gen(ctx) if !assert.NoError(t, err) { return } - bytes, err := os.ReadFile(filepath.Join(unittest.FixturesDir(), name+".yml")) + p := filepath.Join(unittest.FixturesDir(), name+".yml") + bytes, err := os.ReadFile(p) if !assert.NoError(t, err) { return } data := string(util.NormalizeEOL(bytes)) - assert.True(t, data == expected, "Differences detected for %s.yml", name) + assert.EqualValues(t, expected, data, "Differences detected for %s", p) } - test(GetYamlFixturesAccess, "access") + test(db.DefaultContext, GetYamlFixturesAccess, "access") } diff --git a/models/fixtures/access.yml b/models/fixtures/access.yml index 446502843e..1bb6a9a8ac 100644 --- a/models/fixtures/access.yml +++ b/models/fixtures/access.yml @@ -135,4 +135,3 @@ user_id: 31 repo_id: 28 mode: 4 - diff --git a/models/fixtures/access_token.yml b/models/fixtures/access_token.yml index 791b3da1c4..0744255f66 100644 --- a/models/fixtures/access_token.yml +++ b/models/fixtures/access_token.yml @@ -2,7 +2,7 @@ id: 1 uid: 1 name: Token A - #token: d2c6c1ba3890b309189a8e618c72a162e4efbf36 + # token: d2c6c1ba3890b309189a8e618c72a162e4efbf36 token_hash: 2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f token_salt: QuSiZr1byZ token_last_eight: e4efbf36 @@ -13,7 +13,7 @@ id: 2 uid: 1 name: Token B - #token: 4c6f36e6cf498e2a448662f915d932c09c5a146c + # token: 4c6f36e6cf498e2a448662f915d932c09c5a146c token_hash: 1a0e32a231ebbd582dc626c1543a42d3c63d4fa76c07c72862721467c55e8f81c923d60700f0528b5f5f443f055559d3a279 token_salt: Lfwopukrq5 token_last_eight: 9c5a146c @@ -24,10 +24,10 @@ id: 3 uid: 2 name: Token A - #token: 90a18faa671dc43924b795806ffe4fd169d28c91 + # token: 90a18faa671dc43924b795806ffe4fd169d28c91 token_hash: d6d404048048812d9e911d93aefbe94fc768d4876fdf75e3bef0bdc67828e0af422846d3056f2f25ec35c51dc92075685ec5 token_salt: 99ArgXKlQQ token_last_eight: 69d28c91 created_unix: 946687980 updated_unix: 946687980 -#commented out tokens so you can see what they are in plaintext + # commented out tokens so you can see what they are in plaintext diff --git a/models/fixtures/action_runner_token.yml b/models/fixtures/action_runner_token.yml new file mode 100644 index 0000000000..6520b7f6fb --- /dev/null +++ b/models/fixtures/action_runner_token.yml @@ -0,0 +1,35 @@ +- + id: 1 # instance scope + token: xeiWBL5kuTYxGPynHCqQdoeYmJAeG3IzGXCYTrDX + owner_id: 0 + repo_id: 0 + is_active: 1 + created: 1695617748 + updated: 1695617748 + +- + id: 2 # user scope and can't be used + token: vohJB9QcZuSv1gAXESTk2uqpSjHhsKT9j4zYF84x + owner_id: 1 + repo_id: 0 + is_active: 0 + created: 1695617749 + updated: 1695617749 + +- + id: 3 # user scope and can be used + token: gjItAeJ3CA74hNPmPPo0Zco8I1eMaNcP1jVifjOE + owner_id: 1 + repo_id: 0 + is_active: 1 + created: 1695617750 + updated: 1695617750 + +- + id: 4 # repo scope + token: NOjLubxzFxPGhPXflZknys0gjVvQNhomFbAYuhbH + owner_id: 0 + repo_id: 1 + is_active: 1 + created: 1695617751 + updated: 1695617751 diff --git a/models/fixtures/attachment.yml b/models/fixtures/attachment.yml index 9ad43fa2b7..7882d8bff2 100644 --- a/models/fixtures/attachment.yml +++ b/models/fixtures/attachment.yml @@ -140,3 +140,16 @@ download_count: 0 size: 0 created_unix: 946684800 + +- + id: 12 + uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22 + repo_id: 2 + issue_id: 0 + release_id: 11 + uploader_id: 2 + comment_id: 0 + name: README.md + download_count: 0 + size: 0 + created_unix: 946684800 diff --git a/models/fixtures/commit_status_index.yml b/models/fixtures/commit_status_index.yml index 3f252e87ef..f63343b042 100644 --- a/models/fixtures/commit_status_index.yml +++ b/models/fixtures/commit_status_index.yml @@ -2,4 +2,4 @@ id: 1 repo_id: 1 sha: "1234123412341234123412341234123412341234" - max_index: 5 \ No newline at end of file + max_index: 5 diff --git a/models/fixtures/email_address.yml b/models/fixtures/email_address.yml index 1c32379b60..ce4d5208df 100644 --- a/models/fixtures/email_address.yml +++ b/models/fixtures/email_address.yml @@ -73,8 +73,8 @@ - id: 10 uid: 3 - email: user3@example.com - lower_email: user3@example.com + email: org3@example.com + lower_email: org3@example.com is_activated: true is_primary: true @@ -97,16 +97,16 @@ - id: 13 uid: 6 - email: user6@example.com - lower_email: user6@example.com + email: org6@example.com + lower_email: org6@example.com is_activated: true is_primary: true - id: 14 uid: 7 - email: user7@example.com - lower_email: user7@example.com + email: org7@example.com + lower_email: org7@example.com is_activated: true is_primary: true @@ -153,8 +153,8 @@ - id: 20 uid: 17 - email: user17@example.com - lower_email: user17@example.com + email: org17@example.com + lower_email: org17@example.com is_activated: true is_primary: true @@ -169,8 +169,8 @@ - id: 22 uid: 19 - email: user19@example.com - lower_email: user19@example.com + email: org19@example.com + lower_email: org19@example.com is_activated: true is_primary: true diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index fa72f9b647..ccc1fe41fb 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -321,3 +321,20 @@ created_unix: 946684830 updated_unix: 978307200 is_locked: false + +- + id: 20 + repo_id: 23 + index: 1 + poster_id: 2 + original_author_id: 0 + name: issue for pr + content: content + milestone_id: 0 + priority: 0 + is_closed: false + is_pull: true + num_comments: 0 + created_unix: 978307210 + updated_unix: 978307210 + is_locked: false diff --git a/models/fixtures/public_key.yml b/models/fixtures/public_key.yml index 08f5c349a7..ae620ee2d1 100644 --- a/models/fixtures/public_key.yml +++ b/models/fixtures/public_key.yml @@ -8,4 +8,4 @@ type: 1 created_unix: 1559593109 updated_unix: 1565224552 - login_source_id: 0 \ No newline at end of file + login_source_id: 0 diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml index e5589ac703..560674c370 100644 --- a/models/fixtures/pull_request.yml +++ b/models/fixtures/pull_request.yml @@ -89,3 +89,12 @@ base_branch: main merge_base: cbff181af4c9c7fee3cf6c106699e07d9a3f54e6 has_merged: false + +- + id: 8 + type: 0 # gitea pull request + status: 2 # mergable + issue_id: 20 + index: 1 + head_repo_id: 23 + base_repo_id: 23 diff --git a/models/fixtures/reaction.yml b/models/fixtures/reaction.yml index 4925935fe6..ee571a73a4 100644 --- a/models/fixtures/reaction.yml +++ b/models/fixtures/reaction.yml @@ -1,5 +1,5 @@ - - id: 1 #issue reaction + id: 1 # issue reaction type: zzz # not allowed reaction (added before allowed reaction list has changed) issue_id: 1 comment_id: 0 @@ -7,7 +7,7 @@ created_unix: 1573248001 - - id: 2 #issue reaction + id: 2 # issue reaction type: zzz # not allowed reaction (added before allowed reaction list has changed) issue_id: 1 comment_id: 0 @@ -15,7 +15,7 @@ created_unix: 1573248002 - - id: 3 #issue reaction + id: 3 # issue reaction type: eyes # allowed reaction issue_id: 1 comment_id: 0 @@ -23,7 +23,7 @@ created_unix: 1573248003 - - id: 4 #comment reaction + id: 4 # comment reaction type: laugh # allowed reaction issue_id: 1 comment_id: 2 @@ -31,7 +31,7 @@ created_unix: 1573248004 - - id: 5 #comment reaction + id: 5 # comment reaction type: laugh # allowed reaction issue_id: 1 comment_id: 2 diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml index 4ed7df440d..372a79509f 100644 --- a/models/fixtures/release.yml +++ b/models/fixtures/release.yml @@ -136,3 +136,17 @@ is_prerelease: false is_tag: false created_unix: 946684803 + +- id: 11 + repo_id: 2 + publisher_id: 2 + tag_name: "v1.1" + lower_tag_name: "v1.1" + target: "" + title: "v1.1" + sha1: "205ac761f3326a7ebe416e8673760016450b5cec" + num_commits: 2 + is_draft: false + is_prerelease: false + is_tag: false + created_unix: 946684803 diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index c22eb8c2a2..0104419550 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -649,3 +649,23 @@ repo_id: 49 type: 2 created_unix: 946684810 + +- + id: 98 + repo_id: 1 + type: 8 + created_unix: 946684810 + +- + id: 99 + repo_id: 1 + type: 9 + config: "{}" + created_unix: 946684810 + +- + id: 100 + repo_id: 1 + type: 10 + config: "{}" + created_unix: 946684810 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 15668e6cae..373c1caa62 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -64,7 +64,7 @@ - id: 3 owner_id: 3 - owner_name: user3 + owner_name: org3 lower_name: repo3 name: repo3 default_branch: master @@ -126,7 +126,7 @@ - id: 5 owner_id: 3 - owner_name: user3 + owner_name: org3 lower_name: repo5 name: repo5 num_watches: 0 @@ -671,7 +671,7 @@ - id: 23 owner_id: 17 - owner_name: user17 + owner_name: org17 lower_name: big_test_public_4 name: big_test_public_4 num_watches: 0 @@ -679,7 +679,7 @@ num_forks: 0 num_issues: 0 num_closed_issues: 0 - num_pulls: 0 + num_pulls: 1 num_closed_pulls: 0 num_milestones: 0 num_closed_milestones: 0 @@ -701,7 +701,7 @@ - id: 24 owner_id: 17 - owner_name: user17 + owner_name: org17 lower_name: big_test_private_4 name: big_test_private_4 num_watches: 0 @@ -791,7 +791,7 @@ - id: 27 owner_id: 19 - owner_name: user19 + owner_name: org19 lower_name: big_test_public_mirror_6 name: big_test_public_mirror_6 num_watches: 0 @@ -821,7 +821,7 @@ - id: 28 owner_id: 19 - owner_name: user19 + owner_name: org19 lower_name: big_test_private_mirror_6 name: big_test_private_mirror_6 num_watches: 0 @@ -942,7 +942,7 @@ - id: 32 # org public repo owner_id: 3 - owner_name: user3 + owner_name: org3 lower_name: repo21 name: repo21 num_watches: 0 diff --git a/models/fixtures/review.yml b/models/fixtures/review.yml index cc2c7e06e7..7a88080068 100644 --- a/models/fixtures/review.yml +++ b/models/fixtures/review.yml @@ -129,6 +129,44 @@ type: 1 reviewer_id: 6 issue_id: 11 - content: "singular review from user6 and final review for this pr" + content: "singular review from org6 and final review for this pr" updated_unix: 946684831 created_unix: 946684831 + +- + id: 16 + type: 4 + reviewer_id: 20 + issue_id: 20 + content: "review request for user20" + updated_unix: 946684832 + created_unix: 946684832 + +- + id: 17 + type: 1 + reviewer_id: 20 + issue_id: 20 + content: "review approved by user20" + updated_unix: 946684833 + created_unix: 946684833 + +- + id: 18 + type: 4 + reviewer_id: 0 + reviewer_team_id: 5 + issue_id: 20 + content: "review request for team5" + updated_unix: 946684834 + created_unix: 946684834 + +- + id: 19 + type: 4 + reviewer_id: 15 + reviewer_team_id: 0 + issue_id: 20 + content: "review request for user15" + updated_unix: 946684835 + created_unix: 946684835 diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index 65326eedbf..295e51e39c 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -93,7 +93,7 @@ name: review_team authorize: 1 # read num_repos: 1 - num_members: 2 + num_members: 3 includes_all_repositories: false can_create_org_repo: false diff --git a/models/fixtures/team_repo.yml b/models/fixtures/team_repo.yml index a523a90b20..8497720892 100644 --- a/models/fixtures/team_repo.yml +++ b/models/fixtures/team_repo.yml @@ -62,4 +62,4 @@ id: 11 org_id: 17 team_id: 9 - repo_id: 24 \ No newline at end of file + repo_id: 24 diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml index feace5f2a5..9142fe609a 100644 --- a/models/fixtures/team_user.yml +++ b/models/fixtures/team_user.yml @@ -123,3 +123,9 @@ org_id: 36 team_id: 20 uid: 5 + +- + id: 22 + org_id: 17 + team_id: 9 + uid: 15 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index f24d098a7e..fd51379816 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -76,17 +76,17 @@ - id: 3 - lower_name: user3 - name: user3 + lower_name: org3 + name: org3 full_name: ' <<<< >> >> > >> > >>> >> ' - email: user3@example.com + email: org3@example.com keep_email_private: false email_notifications_preference: onmention passwd: ZogKvWdyEx:password passwd_hash_algo: dummy must_change_password: false login_source: 0 - login_name: user3 + login_name: org3 type: 1 salt: ZogKvWdyEx max_repo_creation: -1 @@ -98,7 +98,7 @@ allow_create_organization: true prohibit_login: false avatar: avatar3 - avatar_email: user3@example.com + avatar_email: org3@example.com use_custom_avatar: false num_followers: 0 num_following: 0 @@ -187,17 +187,17 @@ - id: 6 - lower_name: user6 - name: user6 - full_name: User Six - email: user6@example.com + lower_name: org6 + name: org6 + full_name: Org Six + email: org6@example.com keep_email_private: false email_notifications_preference: enabled passwd: ZogKvWdyEx:password passwd_hash_algo: dummy must_change_password: false login_source: 0 - login_name: user6 + login_name: org6 type: 1 salt: ZogKvWdyEx max_repo_creation: -1 @@ -209,7 +209,7 @@ allow_create_organization: true prohibit_login: false avatar: avatar6 - avatar_email: user6@example.com + avatar_email: org6@example.com use_custom_avatar: false num_followers: 0 num_following: 0 @@ -224,17 +224,17 @@ - id: 7 - lower_name: user7 - name: user7 - full_name: User Seven - email: user7@example.com + lower_name: org7 + name: org7 + full_name: Org Seven + email: org7@example.com keep_email_private: false email_notifications_preference: disabled passwd: ZogKvWdyEx:password passwd_hash_algo: dummy must_change_password: false login_source: 0 - login_name: user7 + login_name: org7 type: 1 salt: ZogKvWdyEx max_repo_creation: -1 @@ -246,7 +246,7 @@ allow_create_organization: true prohibit_login: false avatar: avatar7 - avatar_email: user7@example.com + avatar_email: org7@example.com use_custom_avatar: false num_followers: 0 num_following: 0 @@ -594,17 +594,17 @@ - id: 17 - lower_name: user17 - name: user17 - full_name: User 17 - email: user17@example.com + lower_name: org17 + name: org17 + full_name: org 17 + email: org17@example.com keep_email_private: false email_notifications_preference: enabled passwd: ZogKvWdyEx:password passwd_hash_algo: dummy must_change_password: false login_source: 0 - login_name: user17 + login_name: org17 type: 1 salt: ZogKvWdyEx max_repo_creation: -1 @@ -616,7 +616,7 @@ allow_create_organization: true prohibit_login: false avatar: avatar17 - avatar_email: user17@example.com + avatar_email: org17@example.com use_custom_avatar: false num_followers: 0 num_following: 0 @@ -668,17 +668,17 @@ - id: 19 - lower_name: user19 - name: user19 - full_name: User 19 - email: user19@example.com + lower_name: org19 + name: org19 + full_name: Org 19 + email: org19@example.com keep_email_private: false email_notifications_preference: enabled passwd: ZogKvWdyEx:password passwd_hash_algo: dummy must_change_password: false login_source: 0 - login_name: user19 + login_name: org19 type: 1 salt: ZogKvWdyEx max_repo_creation: -1 @@ -690,7 +690,7 @@ allow_create_organization: true prohibit_login: false avatar: avatar19 - avatar_email: user19@example.com + avatar_email: org19@example.com use_custom_avatar: false num_followers: 0 num_following: 0 diff --git a/models/fixtures/watch.yml b/models/fixtures/watch.yml index c29f6bb65a..1950ac99e7 100644 --- a/models/fixtures/watch.yml +++ b/models/fixtures/watch.yml @@ -26,4 +26,4 @@ id: 5 user_id: 11 repo_id: 1 - mode: 3 # auto + mode: 3 # auto diff --git a/models/git/branch_list.go b/models/git/branch_list.go index 131a149782..b5c1301a1d 100644 --- a/models/git/branch_list.go +++ b/models/git/branch_list.go @@ -70,6 +70,7 @@ type FindBranchOptions struct { ExcludeBranchNames []string IsDeletedBranch util.OptionalBool OrderBy string + Keyword string } func (opts *FindBranchOptions) Cond() builder.Cond { @@ -84,6 +85,9 @@ func (opts *FindBranchOptions) Cond() builder.Cond { if !opts.IsDeletedBranch.IsNone() { cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()}) } + if opts.Keyword != "" { + cond = cond.And(builder.Like{"name", opts.Keyword}) + } return cond } diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 24fee8c3b4..c6a52bcd9f 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -235,7 +235,7 @@ func GetCommitStatuses(ctx context.Context, repo *repo_model.Repository, sha str countSession := listCommitStatusesStatement(ctx, repo, sha, opts) countSession = db.SetSessionPagination(countSession, opts) - maxResults, err := countSession.Count(new(CommitStatus)) + maxResults, err := countSession.OrderBy("1").Count(new(CommitStatus)) if err != nil { log.Error("Count PRs: %v", err) return nil, maxResults, err @@ -514,7 +514,7 @@ func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo user_model.ValidateCommitsWithEmails(ctx, commits), repo.GetTrustModel(), func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(repo, user.ID) + return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) }, ), repo, diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index a86941a0fe..2197433b3e 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -31,6 +31,10 @@ func TestGetCommitStatuses(t *testing.T) { assert.Equal(t, structs.CommitStatusPending, statuses[0].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[0].APIURL(db.DefaultContext)) + assert.Equal(t, "cov/awesomeness", statuses[1].Context) + assert.Equal(t, structs.CommitStatusWarning, statuses[1].State) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[1].APIURL(db.DefaultContext)) + assert.Equal(t, "cov/awesomeness", statuses[2].Context) assert.Equal(t, structs.CommitStatusSuccess, statuses[2].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[2].APIURL(db.DefaultContext)) diff --git a/models/git/main_test.go b/models/git/main_test.go index a8658d70c4..aab1fa9a26 100644 --- a/models/git/main_test.go +++ b/models/git/main_test.go @@ -4,7 +4,6 @@ package git_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -15,7 +14,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go index 5ed1003749..5be35f4b11 100644 --- a/models/git/protected_branch.go +++ b/models/git/protected_branch.go @@ -315,6 +315,11 @@ type WhitelistOptions struct { // This function also performs check if whitelist user and team's IDs have been changed // to avoid unnecessary whitelist delete and regenerate. func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) { + err = repo.MustNotBeArchived() + if err != nil { + return err + } + if err = repo.LoadOwner(ctx); err != nil { return fmt.Errorf("LoadOwner: %v", err) } @@ -445,9 +450,14 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre } // DeleteProtectedBranch removes ProtectedBranch relation between the user and repository. -func DeleteProtectedBranch(ctx context.Context, repoID, id int64) (err error) { +func DeleteProtectedBranch(ctx context.Context, repo *repo_model.Repository, id int64) (err error) { + err = repo.MustNotBeArchived() + if err != nil { + return err + } + protectedBranch := &ProtectedBranch{ - RepoID: repoID, + RepoID: repo.ID, ID: id, } diff --git a/models/issues/assignees_test.go b/models/issues/assignees_test.go index 2185f6fc42..3898e814c3 100644 --- a/models/issues/assignees_test.go +++ b/models/issues/assignees_test.go @@ -18,7 +18,7 @@ func TestUpdateAssignee(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // Fake issue with assignees - issue, err := issues_model.GetIssueWithAttrsByID(1) + issue, err := issues_model.GetIssueWithAttrsByID(db.DefaultContext, 1) assert.NoError(t, err) // Assign multiple users @@ -27,9 +27,9 @@ func TestUpdateAssignee(t *testing.T) { _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user2.ID) assert.NoError(t, err) - user3, err := user_model.GetUserByID(db.DefaultContext, 3) + org3, err := user_model.GetUserByID(db.DefaultContext, 3) assert.NoError(t, err) - _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user3.ID) + _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, org3.ID) assert.NoError(t, err) user1, err := user_model.GetUserByID(db.DefaultContext, 1) // This user is already assigned (see the definition in fixtures), so running UpdateAssignee should unassign him @@ -47,7 +47,7 @@ func TestUpdateAssignee(t *testing.T) { assert.NoError(t, err) var expectedAssignees []*user_model.User - expectedAssignees = append(expectedAssignees, user2, user3) + expectedAssignees = append(expectedAssignees, user2, org3) for in, assignee := range issue.Assignees { assert.Equal(t, assignee.ID, expectedAssignees[in].ID) diff --git a/models/issues/comment.go b/models/issues/comment.go index e045b71d23..8e06838f73 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -246,7 +246,7 @@ type Comment struct { NewTitle string OldRef string NewRef string - DependentIssueID int64 + DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue DependentIssue *Issue `xorm:"-"` CommitID int64 @@ -349,7 +349,7 @@ func (c *Comment) LoadPoster(ctx context.Context) (err error) { c.Poster, err = user_model.GetPossibleUserByID(ctx, c.PosterID) if err != nil { if user_model.IsErrUserNotExist(err) { - c.PosterID = -1 + c.PosterID = user_model.GhostUserID c.Poster = user_model.NewGhostUser() } else { log.Error("getUserByID[%d]: %v", c.ID, err) @@ -359,54 +359,54 @@ func (c *Comment) LoadPoster(ctx context.Context) (err error) { } // AfterDelete is invoked from XORM after the object is deleted. -func (c *Comment) AfterDelete() { +func (c *Comment) AfterDelete(ctx context.Context) { if c.ID <= 0 { return } - _, err := repo_model.DeleteAttachmentsByComment(c.ID, true) + _, err := repo_model.DeleteAttachmentsByComment(ctx, c.ID, true) if err != nil { log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err) } } // HTMLURL formats a URL-string to the issue-comment -func (c *Comment) HTMLURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) HTMLURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" } - return c.Issue.HTMLURL() + c.hashLink() + return c.Issue.HTMLURL() + c.hashLink(ctx) } // Link formats a relative URL-string to the issue-comment -func (c *Comment) Link() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) Link(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" } - return c.Issue.Link() + c.hashLink() + return c.Issue.Link() + c.hashLink(ctx) } -func (c *Comment) hashLink() string { +func (c *Comment) hashLink(ctx context.Context) string { if c.Type == CommentTypeCode { if c.ReviewID == 0 { return "/files#" + c.HashTag() } if c.Review == nil { - if err := c.LoadReview(); err != nil { + if err := c.LoadReview(ctx); err != nil { log.Warn("LoadReview(%d): %v", c.ReviewID, err) return "/files#" + c.HashTag() } @@ -419,13 +419,13 @@ func (c *Comment) hashLink() string { } // APIURL formats a API-string to the issue-comment -func (c *Comment) APIURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) APIURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -435,8 +435,8 @@ func (c *Comment) APIURL() string { } // IssueURL formats a URL-string to the issue -func (c *Comment) IssueURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) IssueURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" @@ -446,7 +446,7 @@ func (c *Comment) IssueURL() string { return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -455,14 +455,14 @@ func (c *Comment) IssueURL() string { } // PRURL formats a URL-string to the pull-request -func (c *Comment) PRURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) PRURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -490,9 +490,9 @@ func (c *Comment) EventTag() string { } // LoadLabel if comment.Type is CommentTypeLabel, then load Label -func (c *Comment) LoadLabel() error { +func (c *Comment) LoadLabel(ctx context.Context) error { var label Label - has, err := db.GetEngine(db.DefaultContext).ID(c.LabelID).Get(&label) + has, err := db.GetEngine(ctx).ID(c.LabelID).Get(&label) if err != nil { return err } else if has { @@ -506,10 +506,10 @@ func (c *Comment) LoadLabel() error { } // LoadProject if comment.Type is CommentTypeProject, then load project. -func (c *Comment) LoadProject() error { +func (c *Comment) LoadProject(ctx context.Context) error { if c.OldProjectID > 0 { var oldProject project_model.Project - has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject) + has, err := db.GetEngine(ctx).ID(c.OldProjectID).Get(&oldProject) if err != nil { return err } else if has { @@ -519,7 +519,7 @@ func (c *Comment) LoadProject() error { if c.ProjectID > 0 { var project project_model.Project - has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project) + has, err := db.GetEngine(ctx).ID(c.ProjectID).Get(&project) if err != nil { return err } else if has { @@ -569,8 +569,8 @@ func (c *Comment) LoadAttachments(ctx context.Context) error { } // UpdateAttachments update attachments by UUIDs for the comment -func (c *Comment) UpdateAttachments(uuids []string) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func (c *Comment) UpdateAttachments(ctx context.Context, uuids []string) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -591,11 +591,11 @@ func (c *Comment) UpdateAttachments(uuids []string) error { } // LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees -func (c *Comment) LoadAssigneeUserAndTeam() error { +func (c *Comment) LoadAssigneeUserAndTeam(ctx context.Context) error { var err error if c.AssigneeID > 0 && c.Assignee == nil { - c.Assignee, err = user_model.GetUserByID(db.DefaultContext, c.AssigneeID) + c.Assignee, err = user_model.GetUserByID(ctx, c.AssigneeID) if err != nil { if !user_model.IsErrUserNotExist(err) { return err @@ -603,20 +603,20 @@ func (c *Comment) LoadAssigneeUserAndTeam() error { c.Assignee = user_model.NewGhostUser() } } else if c.AssigneeTeamID > 0 && c.AssigneeTeam == nil { - if err = c.LoadIssue(db.DefaultContext); err != nil { + if err = c.LoadIssue(ctx); err != nil { return err } - if err = c.Issue.LoadRepo(db.DefaultContext); err != nil { + if err = c.Issue.LoadRepo(ctx); err != nil { return err } - if err = c.Issue.Repo.LoadOwner(db.DefaultContext); err != nil { + if err = c.Issue.Repo.LoadOwner(ctx); err != nil { return err } if c.Issue.Repo.Owner.IsOrganization() { - c.AssigneeTeam, err = organization.GetTeamByID(db.DefaultContext, c.AssigneeTeamID) + c.AssigneeTeam, err = organization.GetTeamByID(ctx, c.AssigneeTeamID) if err != nil && !organization.IsErrTeamNotExist(err) { return err } @@ -626,11 +626,11 @@ func (c *Comment) LoadAssigneeUserAndTeam() error { } // LoadResolveDoer if comment.Type is CommentTypeCode and ResolveDoerID not zero, then load resolveDoer -func (c *Comment) LoadResolveDoer() (err error) { +func (c *Comment) LoadResolveDoer(ctx context.Context) (err error) { if c.ResolveDoerID == 0 || c.Type != CommentTypeCode { return nil } - c.ResolveDoer, err = user_model.GetUserByID(db.DefaultContext, c.ResolveDoerID) + c.ResolveDoer, err = user_model.GetUserByID(ctx, c.ResolveDoerID) if err != nil { if user_model.IsErrUserNotExist(err) { c.ResolveDoer = user_model.NewGhostUser() @@ -646,21 +646,21 @@ func (c *Comment) IsResolved() bool { } // LoadDepIssueDetails loads Dependent Issue Details -func (c *Comment) LoadDepIssueDetails() (err error) { +func (c *Comment) LoadDepIssueDetails(ctx context.Context) (err error) { if c.DependentIssueID <= 0 || c.DependentIssue != nil { return nil } - c.DependentIssue, err = GetIssueByID(db.DefaultContext, c.DependentIssueID) + c.DependentIssue, err = GetIssueByID(ctx, c.DependentIssueID) return err } // LoadTime loads the associated time for a CommentTypeAddTimeManual -func (c *Comment) LoadTime() error { +func (c *Comment) LoadTime(ctx context.Context) error { if c.Time != nil || c.TimeID == 0 { return nil } var err error - c.Time, err = GetTrackedTimeByID(c.TimeID) + c.Time, err = GetTrackedTimeByID(ctx, c.TimeID) return err } @@ -683,8 +683,8 @@ func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository } // LoadReactions loads comment reactions -func (c *Comment) LoadReactions(repo *repo_model.Repository) error { - return c.loadReactions(db.DefaultContext, repo) +func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository) error { + return c.loadReactions(ctx, repo) } func (c *Comment) loadReview(ctx context.Context) (err error) { @@ -698,8 +698,8 @@ func (c *Comment) loadReview(ctx context.Context) (err error) { } // LoadReview loads the associated review -func (c *Comment) LoadReview() error { - return c.loadReview(db.DefaultContext) +func (c *Comment) LoadReview(ctx context.Context) error { + return c.loadReview(ctx) } // DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes. @@ -719,13 +719,13 @@ func (c *Comment) UnsignedLine() uint64 { } // CodeCommentLink returns the url to a comment in code -func (c *Comment) CodeCommentLink() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) CodeCommentLink(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -1074,8 +1074,8 @@ func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, } // CountComments count all comments according options by ignoring pagination -func CountComments(opts *FindCommentsOptions) (int64, error) { - sess := db.GetEngine(db.DefaultContext).Where(opts.ToConds()) +func CountComments(ctx context.Context, opts *FindCommentsOptions) (int64, error) { + sess := db.GetEngine(ctx).Where(opts.ToConds()) if opts.RepoID > 0 { sess.Join("INNER", "issue", "issue.id = comment.issue_id") } @@ -1089,8 +1089,8 @@ func UpdateCommentInvalidate(ctx context.Context, c *Comment) error { } // UpdateComment updates information of comment. -func UpdateComment(c *Comment, doer *user_model.User) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateComment(ctx context.Context, c *Comment, doer *user_model.User) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -1147,8 +1147,8 @@ func DeleteComment(ctx context.Context, comment *Comment) error { } // UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id -func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("comment"). +func UpdateCommentsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("comment"). Where(builder.In("issue_id", builder.Select("issue.id"). From("issue"). @@ -1250,7 +1250,7 @@ func (c *Comment) HasOriginalAuthor() bool { } // InsertIssueComments inserts many comments of issues. -func InsertIssueComments(comments []*Comment) error { +func InsertIssueComments(ctx context.Context, comments []*Comment) error { if len(comments) == 0 { return nil } @@ -1260,7 +1260,7 @@ func InsertIssueComments(comments []*Comment) error { issueIDs.Add(comment.IssueID) } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index d447d7542c..25e606f092 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -99,11 +99,11 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu comments[n] = comment n++ - if err := comment.LoadResolveDoer(); err != nil { + if err := comment.LoadResolveDoer(ctx); err != nil { return nil, err } - if err := comment.LoadReactions(issue.Repo); err != nil { + if err := comment.LoadReactions(ctx, issue.Repo); err != nil { return nil, err } @@ -111,7 +111,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ Ctx: ctx, URLPrefix: issue.Repo.Link(), - Metas: issue.Repo.ComposeMetas(), + Metas: issue.Repo.ComposeMetas(ctx), }, comment.Content); err != nil { return nil, err } diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 6f1d350eb4..93af45870e 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/log" ) // CommentList defines a list of comments @@ -422,37 +423,18 @@ func (comments CommentList) loadReviews(ctx context.Context) error { reviewIDs := comments.getReviewIDs() reviews := make(map[int64]*Review, len(reviewIDs)) - left := len(reviewIDs) - for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } - rows, err := db.GetEngine(ctx). - In("id", reviewIDs[:limit]). - Rows(new(Review)) - if err != nil { - return err - } - - for rows.Next() { - var review Review - err = rows.Scan(&review) - if err != nil { - _ = rows.Close() - return err - } - - reviews[review.ID] = &review - } - _ = rows.Close() - - left -= limit - reviewIDs = reviewIDs[limit:] + if err := db.GetEngine(ctx).In("id", reviewIDs).Find(&reviews); err != nil { + return err } for _, comment := range comments { comment.Review = reviews[comment.ReviewID] + if comment.Review == nil { + if comment.ReviewID > 0 { + log.Error("comment with review id [%d] but has no review record", comment.ReviewID) + } + continue + } // If the comment dismisses a review, we need to load the reviewer to show whose review has been dismissed. // Otherwise, the reviewer is the poster of the comment, so we don't need to load it. diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go index 90db476571..c5bbfdedc2 100644 --- a/models/issues/comment_test.go +++ b/models/issues/comment_test.go @@ -89,7 +89,7 @@ func TestMigrate_InsertIssueComments(t *testing.T) { Reactions: []*issues_model.Reaction{reaction}, } - err := issues_model.InsertIssueComments([]*issues_model.Comment{comment}) + err := issues_model.InsertIssueComments(db.DefaultContext, []*issues_model.Comment{comment}) assert.NoError(t, err) issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) diff --git a/models/issues/dependency.go b/models/issues/dependency.go index 4dc5a4aec7..146dd1887d 100644 --- a/models/issues/dependency.go +++ b/models/issues/dependency.go @@ -127,8 +127,8 @@ const ( ) // CreateIssueDependency creates a new dependency for an issue -func CreateIssueDependency(user *user_model.User, issue, dep *Issue) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CreateIssueDependency(ctx context.Context, user *user_model.User, issue, dep *Issue) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -168,8 +168,8 @@ func CreateIssueDependency(user *user_model.User, issue, dep *Issue) error { } // RemoveIssueDependency removes a dependency from an issue -func RemoveIssueDependency(user *user_model.User, issue, dep *Issue, depType DependencyType) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func RemoveIssueDependency(ctx context.Context, user *user_model.User, issue, dep *Issue, depType DependencyType) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/dependency_test.go b/models/issues/dependency_test.go index cdc8e3182d..6eed483cc9 100644 --- a/models/issues/dependency_test.go +++ b/models/issues/dependency_test.go @@ -28,16 +28,16 @@ func TestCreateIssueDependency(t *testing.T) { assert.NoError(t, err) // Create a dependency and check if it was successful - err = issues_model.CreateIssueDependency(user1, issue1, issue2) + err = issues_model.CreateIssueDependency(db.DefaultContext, user1, issue1, issue2) assert.NoError(t, err) // Do it again to see if it will check if the dependency already exists - err = issues_model.CreateIssueDependency(user1, issue1, issue2) + err = issues_model.CreateIssueDependency(db.DefaultContext, user1, issue1, issue2) assert.Error(t, err) assert.True(t, issues_model.IsErrDependencyExists(err)) // Check for circular dependencies - err = issues_model.CreateIssueDependency(user1, issue2, issue1) + err = issues_model.CreateIssueDependency(db.DefaultContext, user1, issue2, issue1) assert.Error(t, err) assert.True(t, issues_model.IsErrCircularDependency(err)) @@ -57,6 +57,6 @@ func TestCreateIssueDependency(t *testing.T) { assert.True(t, left) // Test removing the dependency - err = issues_model.RemoveIssueDependency(user1, issue1, issue2, issues_model.DependencyTypeBlockedBy) + err = issues_model.RemoveIssueDependency(db.DefaultContext, user1, issue1, issue2, issues_model.DependencyTypeBlockedBy) assert.NoError(t, err) } diff --git a/models/issues/issue.go b/models/issues/issue.go index 341ec8547a..8655b9de3f 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -201,12 +201,12 @@ func (issue *Issue) IsTimetrackerEnabled(ctx context.Context) bool { } // GetPullRequest returns the issue pull request -func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { +func (issue *Issue) GetPullRequest(ctx context.Context) (pr *PullRequest, err error) { if !issue.IsPull { return nil, fmt.Errorf("Issue is not a pull request") } - pr, err = GetPullRequestByIssueID(db.DefaultContext, issue.ID) + pr, err = GetPullRequestByIssueID(ctx, issue.ID) if err != nil { return nil, err } @@ -219,7 +219,7 @@ func (issue *Issue) LoadPoster(ctx context.Context) (err error) { if issue.Poster == nil && issue.PosterID != 0 { issue.Poster, err = user_model.GetPossibleUserByID(ctx, issue.PosterID) if err != nil { - issue.PosterID = -1 + issue.PosterID = user_model.GhostUserID issue.Poster = user_model.NewGhostUser() if !user_model.IsErrUserNotExist(err) { return fmt.Errorf("getUserByID.(poster) [%d]: %w", issue.PosterID, err) @@ -369,9 +369,9 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) { } // GetIsRead load the `IsRead` field of the issue -func (issue *Issue) GetIsRead(userID int64) error { +func (issue *Issue) GetIsRead(ctx context.Context, userID int64) error { issueUser := &IssueUser{IssueID: issue.ID, UID: userID} - if has, err := db.GetEngine(db.DefaultContext).Get(issueUser); err != nil { + if has, err := db.GetEngine(ctx).Get(issueUser); err != nil { return err } else if !has { issue.IsRead = false @@ -382,9 +382,9 @@ func (issue *Issue) GetIsRead(userID int64) error { } // APIURL returns the absolute APIURL to this issue. -func (issue *Issue) APIURL() string { +func (issue *Issue) APIURL(ctx context.Context) string { if issue.Repo == nil { - err := issue.LoadRepo(db.DefaultContext) + err := issue.LoadRepo(ctx) if err != nil { log.Error("Issue[%d].APIURL(): %v", issue.ID, err) return "" @@ -479,9 +479,9 @@ func (issue *Issue) GetLastEventLabel() string { } // GetLastComment return last comment for the current issue. -func (issue *Issue) GetLastComment() (*Comment, error) { +func (issue *Issue) GetLastComment(ctx context.Context) (*Comment, error) { var c Comment - exist, err := db.GetEngine(db.DefaultContext).Where("type = ?", CommentTypeComment). + exist, err := db.GetEngine(ctx).Where("type = ?", CommentTypeComment). And("issue_id = ?", issue.ID).Desc("created_unix").Get(&c) if err != nil { return nil, err @@ -543,12 +543,12 @@ func GetIssueByID(ctx context.Context, id int64) (*Issue, error) { } // GetIssueWithAttrsByID returns an issue with attributes by given ID. -func GetIssueWithAttrsByID(id int64) (*Issue, error) { - issue, err := GetIssueByID(db.DefaultContext, id) +func GetIssueWithAttrsByID(ctx context.Context, id int64) (*Issue, error) { + issue, err := GetIssueByID(ctx, id) if err != nil { return nil, err } - return issue, issue.LoadAttributes(db.DefaultContext) + return issue, issue.LoadAttributes(ctx) } // GetIssuesByIDs return issues with the given IDs. @@ -600,8 +600,8 @@ func GetParticipantsIDsByIssueID(ctx context.Context, issueID int64) ([]int64, e } // IsUserParticipantsOfIssue return true if user is participants of an issue -func IsUserParticipantsOfIssue(user *user_model.User, issue *Issue) bool { - userIDs, err := issue.GetParticipantIDsByIssue(db.DefaultContext) +func IsUserParticipantsOfIssue(ctx context.Context, user *user_model.User, issue *Issue) bool { + userIDs, err := issue.GetParticipantIDsByIssue(ctx) if err != nil { log.Error(err.Error()) return false @@ -894,8 +894,8 @@ func IsErrIssueMaxPinReached(err error) bool { } // InsertIssues insert issues to database -func InsertIssues(issues ...*Issue) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func InsertIssues(ctx context.Context, issues ...*Issue) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_index.go b/models/issues/issue_index.go index b480cc683f..16274d0ef0 100644 --- a/models/issues/issue_index.go +++ b/models/issues/issue_index.go @@ -3,12 +3,16 @@ package issues -import "code.gitea.io/gitea/models/db" +import ( + "context" + + "code.gitea.io/gitea/models/db" +) // RecalculateIssueIndexForRepo create issue_index for repo if not exist and // update it based on highest index of existing issues assigned to a repo -func RecalculateIssueIndexForRepo(repoID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_label.go b/models/issues/issue_label.go index 119a13adf2..733f1043b0 100644 --- a/models/issues/issue_label.go +++ b/models/issues/issue_label.go @@ -54,6 +54,8 @@ func newIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m return err } + issue.Labels = append(issue.Labels, label) + return updateLabelCols(ctx, label, "num_issues", "num_closed_issue") } @@ -81,12 +83,12 @@ func RemoveDuplicateExclusiveIssueLabels(ctx context.Context, issue *Issue, labe } // NewIssueLabel creates a new issue-label relation. -func NewIssueLabel(issue *Issue, label *Label, doer *user_model.User) (err error) { - if HasIssueLabel(db.DefaultContext, issue.ID, label.ID) { +func NewIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) (err error) { + if HasIssueLabel(ctx, issue.ID, label.ID) { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -122,6 +124,11 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us if err = issue.LoadRepo(ctx); err != nil { return err } + + if err = issue.LoadLabels(ctx); err != nil { + return err + } + for _, l := range labels { // Don't add already present labels and invalid labels if HasIssueLabel(ctx, issue.ID, l.ID) || @@ -129,6 +136,10 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us continue } + if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, l, doer); err != nil { + return err + } + if err = newIssueLabel(ctx, issue, l, doer); err != nil { return fmt.Errorf("newIssueLabel: %w", err) } @@ -138,8 +149,8 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us } // NewIssueLabels creates a list of issue-label relations. -func NewIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func NewIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -348,8 +359,8 @@ func clearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) // ClearIssueLabels removes all issue labels as the given user. // Triggers appropriate WebHooks, if any. -func ClearIssueLabels(issue *Issue, doer *user_model.User) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ClearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -421,8 +432,8 @@ func RemoveDuplicateExclusiveLabels(labels []*Label) []*Label { // ReplaceIssueLabels removes all current labels and add new labels to the issue. // Triggers appropriate WebHooks, if any. -func ReplaceIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ReplaceIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_label_test.go b/models/issues/issue_label_test.go new file mode 100644 index 0000000000..0470b99e24 --- /dev/null +++ b/models/issues/issue_label_test.go @@ -0,0 +1,29 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issues_test + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +func TestNewIssueLabelsScope(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 18}) + label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 7}) + label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 8}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + assert.NoError(t, issues_model.NewIssueLabels(db.DefaultContext, issue, []*issues_model.Label{label1, label2}, doer)) + + assert.Len(t, issue.Labels, 1) + assert.Equal(t, label2.ID, issue.Labels[0].ID) +} diff --git a/models/issues/issue_lock.go b/models/issues/issue_lock.go index 19cd6d3167..b21629b529 100644 --- a/models/issues/issue_lock.go +++ b/models/issues/issue_lock.go @@ -4,6 +4,8 @@ package issues import ( + "context" + "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" ) @@ -17,16 +19,16 @@ type IssueLockOptions struct { // LockIssue locks an issue. This would limit commenting abilities to // users with write access to the repo -func LockIssue(opts *IssueLockOptions) error { - return updateIssueLock(opts, true) +func LockIssue(ctx context.Context, opts *IssueLockOptions) error { + return updateIssueLock(ctx, opts, true) } // UnlockIssue unlocks a previously locked issue. -func UnlockIssue(opts *IssueLockOptions) error { - return updateIssueLock(opts, false) +func UnlockIssue(ctx context.Context, opts *IssueLockOptions) error { + return updateIssueLock(ctx, opts, false) } -func updateIssueLock(opts *IssueLockOptions, lock bool) error { +func updateIssueLock(ctx context.Context, opts *IssueLockOptions, lock bool) error { if opts.Issue.IsLocked == lock { return nil } @@ -39,7 +41,7 @@ func updateIssueLock(opts *IssueLockOptions, lock bool) error { commentType = CommentTypeUnlock } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index ed249527bf..cc7ffb356a 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -38,11 +38,7 @@ func (issue *Issue) projectID(ctx context.Context) int64 { } // ProjectBoardID return project board id if issue was assigned to one -func (issue *Issue) ProjectBoardID() int64 { - return issue.projectBoardID(db.DefaultContext) -} - -func (issue *Issue) projectBoardID(ctx context.Context) int64 { +func (issue *Issue) ProjectBoardID(ctx context.Context) int64 { var ip project_model.ProjectIssue has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip) if err != nil || !has { @@ -55,7 +51,7 @@ func (issue *Issue) projectBoardID(ctx context.Context) int64 { func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) { issueList := make(IssueList, 0, 10) - if b.ID != 0 { + if b.ID > 0 { issues, err := Issues(ctx, &IssuesOptions{ ProjectBoardID: b.ID, ProjectID: b.ProjectID, @@ -69,7 +65,7 @@ func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList if b.Default { issues, err := Issues(ctx, &IssuesOptions{ - ProjectBoardID: -1, // Issues without ProjectBoardID + ProjectBoardID: db.NoConditionID, ProjectID: b.ProjectID, SortType: "project-column-sorting", }) @@ -100,8 +96,8 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m } // ChangeProjectAssign changes the project associated with an issue -func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -154,30 +150,3 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U ProjectID: newProjectID, }) } - -// MoveIssueAcrossProjectBoards move a card from one board to another -func MoveIssueAcrossProjectBoards(issue *Issue, board *project_model.Board) error { - ctx, committer, err := db.TxContext(db.DefaultContext) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) - - var pis project_model.ProjectIssue - has, err := sess.Where("issue_id=?", issue.ID).Get(&pis) - if err != nil { - return err - } - - if !has { - return fmt.Errorf("issue has to be added to a project first") - } - - pis.ProjectBoardID = board.ID - if _, err := sess.ID(pis.ID).Cols("project_board_id").Update(&pis); err != nil { - return err - } - - return committer.Commit() -} diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 5d40b44704..9b6bf117b1 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -180,6 +180,17 @@ func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sessio return sess } +func applyProjectBoardCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session { + // opts.ProjectBoardID == 0 means all project boards, + // do not need to apply any condition + if opts.ProjectBoardID > 0 { + sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID})) + } else if opts.ProjectBoardID == db.NoConditionID { + sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Neq{"project_board_id": 0})) + } + return sess +} + func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session { if len(opts.RepoIDs) == 1 { opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]} @@ -240,13 +251,7 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session { applyProjectCondition(sess, opts) - if opts.ProjectBoardID != 0 { - if opts.ProjectBoardID > 0 { - sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID})) - } else { - sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0})) - } - } + applyProjectBoardCondition(sess, opts) switch opts.IsPull { case util.OptionalBoolTrue: @@ -362,14 +367,21 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) From("team_user"). Where(builder.Eq{"team_user.uid": reviewRequestedID}) + // if the review is approved or rejected, it should not be shown in the review requested list + maxReview := builder.Select("MAX(r.id)"). + From("review as r"). + Where(builder.In("r.type", []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest})). + GroupBy("r.issue_id, r.reviewer_id, r.reviewer_team_id") + subQuery := builder.Select("review.issue_id"). From("review"). Where(builder.And( - builder.In("review.type", []ReviewType{ReviewTypeRequest, ReviewTypeReject, ReviewTypeApprove}), + builder.Eq{"review.type": ReviewTypeRequest}, builder.Or( builder.Eq{"review.reviewer_id": reviewRequestedID}, builder.In("review.reviewer_team_id", existInTeamQuery), ), + builder.In("review.id", maxReview), )) return sess.Where("issue.poster_id <> ?", reviewRequestedID). And(builder.In("issue.id", subQuery)) @@ -437,9 +449,9 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Sess } // GetRepoIDsForIssuesOptions find all repo ids for the given options -func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *user_model.User) ([]int64, error) { +func GetRepoIDsForIssuesOptions(ctx context.Context, opts *IssuesOptions, user *user_model.User) ([]int64, error) { repoIDs := make([]int64, 0, 5) - e := db.GetEngine(db.DefaultContext) + e := db.GetEngine(ctx) sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index 1654e6ce75..99ca19f804 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -5,7 +5,6 @@ package issues import ( "context" - "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -81,9 +80,9 @@ func CountIssues(ctx context.Context, opts *IssuesOptions) (int64, error) { } // GetIssueStats returns issue statistic information by given conditions. -func GetIssueStats(opts *IssuesOptions) (*IssueStats, error) { +func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error) { if len(opts.IssueIDs) <= MaxQueryParameters { - return getIssueStatsChunk(opts, opts.IssueIDs) + return getIssueStatsChunk(ctx, opts, opts.IssueIDs) } // If too long a list of IDs is provided, we get the statistics in @@ -96,7 +95,7 @@ func GetIssueStats(opts *IssuesOptions) (*IssueStats, error) { if chunk > len(opts.IssueIDs) { chunk = len(opts.IssueIDs) } - stats, err := getIssueStatsChunk(opts, opts.IssueIDs[i:chunk]) + stats, err := getIssueStatsChunk(ctx, opts, opts.IssueIDs[i:chunk]) if err != nil { return nil, err } @@ -113,10 +112,10 @@ func GetIssueStats(opts *IssuesOptions) (*IssueStats, error) { return accum, nil } -func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, error) { +func getIssueStatsChunk(ctx context.Context, opts *IssuesOptions, issueIDs []int64) (*IssueStats, error) { stats := &IssueStats{} - sess := db.GetEngine(db.DefaultContext). + sess := db.GetEngine(ctx). Join("INNER", "repository", "`issue`.repo_id = `repository`.id") var err error @@ -181,195 +180,6 @@ func applyIssuesOptions(sess *xorm.Session, opts *IssuesOptions, issueIDs []int6 return sess } -// GetUserIssueStats returns issue statistic information for dashboard by given conditions. -func GetUserIssueStats(filterMode int, opts IssuesOptions) (*IssueStats, error) { - if opts.User == nil { - return nil, errors.New("issue stats without user") - } - if opts.IsPull.IsNone() { - return nil, errors.New("unaccepted ispull option") - } - - var err error - stats := &IssueStats{} - - cond := builder.NewCond() - - cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()}) - - if len(opts.RepoIDs) > 0 { - cond = cond.And(builder.In("issue.repo_id", opts.RepoIDs)) - } - if len(opts.IssueIDs) > 0 { - cond = cond.And(builder.In("issue.id", opts.IssueIDs)) - } - if opts.RepoCond != nil { - cond = cond.And(opts.RepoCond) - } - - if opts.User != nil { - cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue())) - } - - sess := func(cond builder.Cond) *xorm.Session { - s := db.GetEngine(db.DefaultContext). - Join("INNER", "repository", "`issue`.repo_id = `repository`.id"). - Where(cond) - if len(opts.LabelIDs) > 0 { - s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id"). - In("issue_label.label_id", opts.LabelIDs) - } - - if opts.IsArchived != util.OptionalBoolNone { - s.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()}) - } - return s - } - - switch filterMode { - case FilterModeAll, FilterModeYourRepositories: - stats.OpenCount, err = sess(cond). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = sess(cond). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeAssign: - stats.OpenCount, err = applyAssigneeCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyAssigneeCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeCreate: - stats.OpenCount, err = applyPosterCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyPosterCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeMention: - stats.OpenCount, err = applyMentionedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyMentionedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeReviewRequested: - stats.OpenCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeReviewed: - stats.OpenCount, err = applyReviewedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyReviewedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - } - - cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed.IsTrue()}) - stats.AssignCount, err = applyAssigneeCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.CreateCount, err = applyPosterCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.MentionCount, err = applyMentionedCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.YourRepositoriesCount, err = sess(cond).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.ReviewRequestedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.ReviewedCount, err = applyReviewedCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - return stats, nil -} - -// GetRepoIssueStats returns number of open and closed repository issues by given filter mode. -func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen, numClosed int64) { - countSession := func(isClosed, isPull bool, repoID int64) *xorm.Session { - sess := db.GetEngine(db.DefaultContext). - Where("is_closed = ?", isClosed). - And("is_pull = ?", isPull). - And("repo_id = ?", repoID) - - return sess - } - - openCountSession := countSession(false, isPull, repoID) - closedCountSession := countSession(true, isPull, repoID) - - switch filterMode { - case FilterModeAssign: - applyAssigneeCondition(openCountSession, uid) - applyAssigneeCondition(closedCountSession, uid) - case FilterModeCreate: - applyPosterCondition(openCountSession, uid) - applyPosterCondition(closedCountSession, uid) - } - - openResult, _ := openCountSession.Count(new(Issue)) - closedResult, _ := closedCountSession.Count(new(Issue)) - - return openResult, closedResult -} - // CountOrphanedIssues count issues without a repo func CountOrphanedIssues(ctx context.Context) (int64, error) { return db.GetEngine(ctx). diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index f1bccc0cf8..4393d18bcf 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -13,12 +13,10 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" - "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" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" "xorm.io/builder" @@ -36,7 +34,7 @@ func TestIssue_ReplaceLabels(t *testing.T) { for i, labelID := range labelIDs { labels[i] = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID, RepoID: repo.ID}) } - assert.NoError(t, issues_model.ReplaceIssueLabels(issue, labels, doer)) + assert.NoError(t, issues_model.ReplaceIssueLabels(db.DefaultContext, issue, labels, doer)) unittest.AssertCount(t, &issues_model.IssueLabel{IssueID: issueID}, len(expectedLabelIDs)) for _, labelID := range expectedLabelIDs { unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: labelID}) @@ -67,7 +65,7 @@ func TestIssueAPIURL(t *testing.T) { err := issue.LoadAttributes(db.DefaultContext) assert.NoError(t, err) - assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL()) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL(db.DefaultContext)) } func TestGetIssuesByIDs(t *testing.T) { @@ -124,7 +122,7 @@ func TestIssue_ClearLabels(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID}) doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.doerID}) - assert.NoError(t, issues_model.ClearIssueLabels(issue, doer)) + assert.NoError(t, issues_model.ClearIssueLabels(db.DefaultContext, issue, doer)) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: test.issueID}) } } @@ -193,6 +191,12 @@ func TestIssues(t *testing.T) { }, []int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests }, + { + issues_model.IssuesOptions{ + MilestoneIDs: []int64{1}, + }, + []int64{2}, + }, } { issues, err := issues_model.Issues(db.DefaultContext, &test.Opts) assert.NoError(t, err) @@ -204,128 +208,6 @@ func TestIssues(t *testing.T) { } } -func TestGetUserIssueStats(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - for _, test := range []struct { - FilterMode int - Opts issues_model.IssuesOptions - ExpectedIssueStats issues_model.IssueStats - }{ - { - issues_model.FilterModeAll, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - RepoIDs: []int64{1}, - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - OpenCount: 1, // 6 - ClosedCount: 1, // 1 - }, - }, - { - issues_model.FilterModeAll, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - RepoIDs: []int64{1}, - IsPull: util.OptionalBoolFalse, - IsClosed: util.OptionalBoolTrue, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 0, - CreateCount: 0, - OpenCount: 1, // 6 - ClosedCount: 1, // 1 - }, - }, - { - issues_model.FilterModeAssign, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - OpenCount: 1, // 6 - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeCreate, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - OpenCount: 1, // 6 - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeMention, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - MentionCount: 0, - OpenCount: 0, - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeCreate, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IssueIDs: []int64{1}, - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 1 - AssignCount: 1, // 1 - CreateCount: 1, // 1 - OpenCount: 1, // 1 - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeAll, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}), - Org: unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}), - Team: unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 2, - AssignCount: 1, - CreateCount: 1, - OpenCount: 2, - }, - }, - } { - t.Run(fmt.Sprintf("%#v", test.Opts), func(t *testing.T) { - stats, err := issues_model.GetUserIssueStats(test.FilterMode, test.Opts) - if !assert.NoError(t, err) { - return - } - assert.Equal(t, test.ExpectedIssueStats, *stats) - }) - } -} - func TestIssue_loadTotalTimes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) ms, err := issues_model.GetIssueByID(db.DefaultContext, 2) @@ -354,7 +236,7 @@ func TestGetRepoIDsForIssuesOptions(t *testing.T) { []int64{1, 2}, }, } { - repoIDs, err := issues_model.GetRepoIDsForIssuesOptions(&test.Opts, user) + repoIDs, err := issues_model.GetRepoIDsForIssuesOptions(db.DefaultContext, &test.Opts, user) assert.NoError(t, err) if assert.Len(t, repoIDs, len(test.ExpectedRepoIDs)) { for i, repoID := range repoIDs { @@ -377,7 +259,7 @@ func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *is Title: title, Content: content, } - err := issues_model.NewIssue(repo, &issue, nil, nil) + err := issues_model.NewIssue(db.DefaultContext, repo, &issue, nil, nil) assert.NoError(t, err) has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue) @@ -430,11 +312,11 @@ func TestIssue_ResolveMentions(t *testing.T) { // Public repo, doer testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{}) // Private repo, team member - testSuccess("user17", "big_test_private_4", "user20", []string{"user2"}, []int64{2}) + testSuccess("org17", "big_test_private_4", "user20", []string{"user2"}, []int64{2}) // Private repo, not a team member - testSuccess("user17", "big_test_private_4", "user20", []string{"user5"}, []int64{}) + testSuccess("org17", "big_test_private_4", "user20", []string{"user5"}, []int64{}) // Private repo, whole team - testSuccess("user17", "big_test_private_4", "user15", []string{"user17/owners"}, []int64{18}) + testSuccess("org17", "big_test_private_4", "user15", []string{"org17/owners"}, []int64{18}) } func TestResourceIndex(t *testing.T) { @@ -493,7 +375,7 @@ func TestCorrectIssueStats(t *testing.T) { // Now we will call the GetIssueStats with these IDs and if working, // get the correct stats back. - issueStats, err := issues_model.GetIssueStats(&issues_model.IssuesOptions{ + issueStats, err := issues_model.GetIssueStats(db.DefaultContext, &issues_model.IssuesOptions{ RepoIDs: []int64{1}, IssueIDs: ids, }) @@ -509,7 +391,7 @@ func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}), } - assert.NoError(t, miles.LoadTotalTrackedTimes()) + assert.NoError(t, miles.LoadTotalTrackedTimes(db.DefaultContext)) assert.Equal(t, int64(3682), miles[0].TotalTrackedTime) } @@ -518,7 +400,7 @@ func TestLoadTotalTrackedTime(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - assert.NoError(t, milestone.LoadTotalTrackedTime()) + assert.NoError(t, milestone.LoadTotalTrackedTime(db.DefaultContext)) assert.Equal(t, int64(3682), milestone.TotalTrackedTime) } @@ -527,7 +409,7 @@ func TestCountIssues(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 19, count) + assert.EqualValues(t, 20, count) } func TestIssueLoadAttributes(t *testing.T) { @@ -601,7 +483,7 @@ func assertCreateIssues(t *testing.T, isPull bool) { Labels: []*issues_model.Label{label}, Reactions: []*issues_model.Reaction{reaction}, } - err := issues_model.InsertIssues(is) + err := issues_model.InsertIssues(db.DefaultContext, is) assert.NoError(t, err) i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title}) diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 9607b21a67..b258dc882c 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -165,8 +165,8 @@ func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User, } // ChangeIssueRef changes the branch of this issue, as the given user. -func ChangeIssueRef(issue *Issue, doer *user_model.User, oldRef string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeIssueRef(ctx context.Context, issue *Issue, doer *user_model.User, oldRef string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -215,8 +215,8 @@ func AddDeletePRBranchComment(ctx context.Context, doer *user_model.User, repo * } // UpdateIssueAttachments update attachments by UUIDs for the issue -func UpdateIssueAttachments(issueID int64, uuids []string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -235,8 +235,8 @@ func UpdateIssueAttachments(issueID int64, uuids []string) (err error) { } // ChangeIssueContent changes issue content, as the given user. -func ChangeIssueContent(issue *Issue, doer *user_model.User, content string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User, content string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -381,8 +381,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue } // NewIssue creates new issue with labels for repository. -func NewIssue(repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -432,8 +432,8 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo // UpdateIssueByAPI updates all allowed fields of given issue. // If the issue status is changed a statusChangeComment is returned // similarly if the title is changed the titleChanged bool is set to true -func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, false, err } @@ -486,12 +486,12 @@ func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment } // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. -func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) { +func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) { // if the deadline hasn't changed do nothing if issue.DeadlineUnix == deadlineUnix { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -510,17 +510,6 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *us return committer.Commit() } -// DeleteInIssue delete records in beans with external key issue_id = ? -func DeleteInIssue(ctx context.Context, issueID int64, beans ...any) error { - e := db.GetEngine(ctx) - for _, bean := range beans { - if _, err := e.In("issue_id", issueID).Delete(bean); err != nil { - return err - } - } - return nil -} - // FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database. func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_model.User, content string) (mentions []*user_model.User, err error) { rawMentions := references.FindAllMentionsMarkdown(content) @@ -669,8 +658,8 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u } // UpdateIssuesMigrationsByType updates all migrated repositories' issues from gitServiceType to replace originalAuthorID to posterID -func UpdateIssuesMigrationsByType(gitServiceType api.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("issue"). +func UpdateIssuesMigrationsByType(ctx context.Context, gitServiceType api.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("issue"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). Update(map[string]any{ @@ -682,8 +671,8 @@ func UpdateIssuesMigrationsByType(gitServiceType api.GitServiceType, originalAut } // UpdateReactionsMigrationsByType updates all migrated repositories' reactions from gitServiceType to replace originalAuthorID to posterID -func UpdateReactionsMigrationsByType(gitServiceType api.GitServiceType, originalAuthorID string, userID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("reaction"). +func UpdateReactionsMigrationsByType(ctx context.Context, gitServiceType api.GitServiceType, originalAuthorID string, userID int64) error { + _, err := db.GetEngine(ctx).Table("reaction"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(gitServiceType)). Update(map[string]any{ @@ -696,85 +685,100 @@ func UpdateReactionsMigrationsByType(gitServiceType api.GitServiceType, original // DeleteIssuesByRepoID deletes issues by repositories id func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths []string, err error) { - deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"issue.repo_id": repoID}) - + // MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289 + // so here it uses "DELETE ... WHERE IN" with pre-queried IDs. sess := db.GetEngine(ctx) - // Delete content histories - if _, err = sess.In("issue_id", deleteCond). - Delete(&ContentHistory{}); err != nil { - return nil, err - } - // Delete comments and attachments - if _, err = sess.In("issue_id", deleteCond). - Delete(&Comment{}); err != nil { - return nil, err - } + for { + issueIDs := make([]int64, 0, db.DefaultMaxInSize) - // Dependencies for issues in this repository - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueDependency{}); err != nil { - return nil, err - } + err := sess.Table(&Issue{}).Where("repo_id = ?", repoID).OrderBy("id").Limit(db.DefaultMaxInSize).Cols("id").Find(&issueIDs) + if err != nil { + return nil, err + } - // Delete dependencies for issues in other repositories - if _, err = sess.In("dependency_id", deleteCond). - Delete(&IssueDependency{}); err != nil { - return nil, err - } + if len(issueIDs) == 0 { + break + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueUser{}); err != nil { - return nil, err - } + // Delete content histories + _, err = sess.In("issue_id", issueIDs).Delete(&ContentHistory{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&Reaction{}); err != nil { - return nil, err - } + // Delete comments and attachments + _, err = sess.In("issue_id", issueIDs).Delete(&Comment{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueWatch{}); err != nil { - return nil, err - } + // Dependencies for issues in this repository + _, err = sess.In("issue_id", issueIDs).Delete(&IssueDependency{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&Stopwatch{}); err != nil { - return nil, err - } + // Delete dependencies for issues in other repositories + _, err = sess.In("dependency_id", issueIDs).Delete(&IssueDependency{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&TrackedTime{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&project_model.ProjectIssue{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}) + if err != nil { + return nil, err + } - if _, err = sess.In("dependent_issue_id", deleteCond). - Delete(&Comment{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{}) + if err != nil { + return nil, err + } - var attachments []*repo_model.Attachment - if err = sess.In("issue_id", deleteCond). - Find(&attachments); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&Stopwatch{}) + if err != nil { + return nil, err + } - for j := range attachments { - attachmentPaths = append(attachmentPaths, attachments[j].RelativePath()) - } + _, err = sess.In("issue_id", issueIDs).Delete(&TrackedTime{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&repo_model.Attachment{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&project_model.ProjectIssue{}) + if err != nil { + return nil, err + } - if _, err = db.DeleteByBean(ctx, &Issue{RepoID: repoID}); err != nil { - return nil, err + _, err = sess.In("dependent_issue_id", issueIDs).Delete(&Comment{}) + if err != nil { + return nil, err + } + + var attachments []*repo_model.Attachment + err = sess.In("issue_id", issueIDs).Find(&attachments) + if err != nil { + return nil, err + } + + for j := range attachments { + attachmentPaths = append(attachmentPaths, attachments[j].RelativePath()) + } + + _, err = sess.In("issue_id", issueIDs).Delete(&repo_model.Attachment{}) + if err != nil { + return nil, err + } + + _, err = sess.In("id", issueIDs).Delete(&Issue{}) + if err != nil { + return nil, err + } } return attachmentPaths, err @@ -809,7 +813,7 @@ func DeleteOrphanedIssues(ctx context.Context) error { // Remove issue attachment files. for i := range attachmentPaths { - system_model.RemoveAllWithNotice(db.DefaultContext, "Delete issue attachment", attachmentPaths[i]) + system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i]) } return nil } diff --git a/models/issues/issue_user.go b/models/issues/issue_user.go index d053b1d543..24bb74648d 100644 --- a/models/issues/issue_user.go +++ b/models/issues/issue_user.go @@ -15,7 +15,7 @@ import ( type IssueUser struct { ID int64 `xorm:"pk autoincr"` UID int64 `xorm:"INDEX"` // User ID. - IssueID int64 + IssueID int64 `xorm:"INDEX"` IsRead bool IsMentioned bool } diff --git a/models/issues/issue_watch.go b/models/issues/issue_watch.go index 1efc0ea687..9e616a0eb1 100644 --- a/models/issues/issue_watch.go +++ b/models/issues/issue_watch.go @@ -30,8 +30,8 @@ func init() { type IssueWatchList []*IssueWatch // CreateOrUpdateIssueWatch set watching for a user and issue -func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { - iw, exists, err := GetIssueWatch(db.DefaultContext, userID, issueID) +func CreateOrUpdateIssueWatch(ctx context.Context, userID, issueID int64, isWatching bool) error { + iw, exists, err := GetIssueWatch(ctx, userID, issueID) if err != nil { return err } @@ -43,13 +43,13 @@ func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { IsWatching: isWatching, } - if _, err := db.GetEngine(db.DefaultContext).Insert(iw); err != nil { + if _, err := db.GetEngine(ctx).Insert(iw); err != nil { return err } } else { iw.IsWatching = isWatching - if _, err := db.GetEngine(db.DefaultContext).ID(iw.ID).Cols("is_watching", "updated_unix").Update(iw); err != nil { + if _, err := db.GetEngine(ctx).ID(iw.ID).Cols("is_watching", "updated_unix").Update(iw); err != nil { return err } } @@ -69,19 +69,19 @@ func GetIssueWatch(ctx context.Context, userID, issueID int64) (iw *IssueWatch, // CheckIssueWatch check if an user is watching an issue // it takes participants and repo watch into account -func CheckIssueWatch(user *user_model.User, issue *Issue) (bool, error) { - iw, exist, err := GetIssueWatch(db.DefaultContext, user.ID, issue.ID) +func CheckIssueWatch(ctx context.Context, user *user_model.User, issue *Issue) (bool, error) { + iw, exist, err := GetIssueWatch(ctx, user.ID, issue.ID) if err != nil { return false, err } if exist { return iw.IsWatching, nil } - w, err := repo_model.GetWatch(db.DefaultContext, user.ID, issue.RepoID) + w, err := repo_model.GetWatch(ctx, user.ID, issue.RepoID) if err != nil { return false, err } - return repo_model.IsWatchMode(w.Mode) || IsUserParticipantsOfIssue(user, issue), nil + return repo_model.IsWatchMode(w.Mode) || IsUserParticipantsOfIssue(ctx, user, issue), nil } // GetIssueWatchersIDs returns IDs of subscribers or explicit unsubscribers to a given issue id diff --git a/models/issues/issue_watch_test.go b/models/issues/issue_watch_test.go index 4f44487f56..d4ce8d8d3d 100644 --- a/models/issues/issue_watch_test.go +++ b/models/issues/issue_watch_test.go @@ -16,11 +16,11 @@ import ( func TestCreateOrUpdateIssueWatch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(3, 1, true)) + assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(db.DefaultContext, 3, 1, true)) iw := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 3, IssueID: 1}) assert.True(t, iw.IsWatching) - assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(1, 1, false)) + assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(db.DefaultContext, 1, 1, false)) iw = unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 1, IssueID: 1}) assert.False(t, iw.IsWatching) } diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go index a1086f9e81..77ef53a013 100644 --- a/models/issues/issue_xref.go +++ b/models/issues/issue_xref.go @@ -252,22 +252,22 @@ func (c *Comment) neuterCrossReferences(ctx context.Context) error { } // LoadRefComment loads comment that created this reference from database -func (c *Comment) LoadRefComment() (err error) { +func (c *Comment) LoadRefComment(ctx context.Context) (err error) { if c.RefComment != nil { return nil } - c.RefComment, err = GetCommentByID(db.DefaultContext, c.RefCommentID) + c.RefComment, err = GetCommentByID(ctx, c.RefCommentID) return err } // LoadRefIssue loads comment that created this reference from database -func (c *Comment) LoadRefIssue() (err error) { +func (c *Comment) LoadRefIssue(ctx context.Context) (err error) { if c.RefIssue != nil { return nil } - c.RefIssue, err = GetIssueByID(db.DefaultContext, c.RefIssueID) + c.RefIssue, err = GetIssueByID(ctx, c.RefIssueID) if err == nil { - err = c.RefIssue.LoadRepo(db.DefaultContext) + err = c.RefIssue.LoadRepo(ctx) } return err } @@ -278,21 +278,21 @@ func CommentTypeIsRef(t CommentType) bool { } // RefCommentLink returns the relative URL for the comment that created this reference -func (c *Comment) RefCommentLink() string { +func (c *Comment) RefCommentLink(ctx context.Context) string { // Edge case for when the reference is inside the title or the description of the referring issue if c.RefCommentID == 0 { - return c.RefIssueLink() + return c.RefIssueLink(ctx) } - if err := c.LoadRefComment(); err != nil { // Silently dropping errors :unamused: + if err := c.LoadRefComment(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefComment(%d): %v", c.RefCommentID, err) return "" } - return c.RefComment.Link() + return c.RefComment.Link(ctx) } // RefIssueLink returns the relative URL of the issue where this reference was created -func (c *Comment) RefIssueLink() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: +func (c *Comment) RefIssueLink(ctx context.Context) string { + if err := c.LoadRefIssue(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) return "" } @@ -300,8 +300,8 @@ func (c *Comment) RefIssueLink() string { } // RefIssueTitle returns the title of the issue where this reference was created -func (c *Comment) RefIssueTitle() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: +func (c *Comment) RefIssueTitle(ctx context.Context) string { + if err := c.LoadRefIssue(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) return "" } @@ -309,8 +309,8 @@ func (c *Comment) RefIssueTitle() string { } // RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created -func (c *Comment) RefIssueIdent() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: +func (c *Comment) RefIssueIdent(ctx context.Context) string { + if err := c.LoadRefIssue(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) return "" } diff --git a/models/issues/issue_xref_test.go b/models/issues/issue_xref_test.go index 6e94c26272..5bcaf75518 100644 --- a/models/issues/issue_xref_test.go +++ b/models/issues/issue_xref_test.go @@ -54,7 +54,7 @@ func TestXRef_AddCrossReferences(t *testing.T) { itarget = testCreateIssue(t, 3, 3, "title4", "content4", false) // Cross-reference to issue #4 by admin - content = fmt.Sprintf("content5, mentions user3/repo3#%d", itarget.Index) + content = fmt.Sprintf("content5, mentions org3/repo3#%d", itarget.Index) i = testCreateIssue(t, 2, 1, "title5", content, false) ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}) assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type) @@ -63,7 +63,7 @@ func TestXRef_AddCrossReferences(t *testing.T) { assert.Equal(t, references.XRefActionNone, ref.RefAction) // Cross-reference to issue #4 with no permission - content = fmt.Sprintf("content6, mentions user3/repo3#%d", itarget.Index) + content = fmt.Sprintf("content6, mentions org3/repo3#%d", itarget.Index) i = testCreateIssue(t, 4, 5, "title6", content, false) unittest.AssertNotExistsBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}) } diff --git a/models/issues/label.go b/models/issues/label.go index 0087c933a6..f8dbb9e39c 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -199,8 +199,8 @@ func NewLabel(ctx context.Context, l *Label) error { } // NewLabels creates new labels -func NewLabels(labels ...*Label) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func NewLabels(ctx context.Context, labels ...*Label) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -221,19 +221,19 @@ func NewLabels(labels ...*Label) error { } // UpdateLabel updates label information. -func UpdateLabel(l *Label) error { +func UpdateLabel(ctx context.Context, l *Label) error { color, err := label.NormalizeColor(l.Color) if err != nil { return err } l.Color = color - return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive", "archived_unix") + return updateLabelCols(ctx, l, "name", "description", "color", "exclusive", "archived_unix") } // DeleteLabel delete a label -func DeleteLabel(id, labelID int64) error { - l, err := GetLabelByID(db.DefaultContext, labelID) +func DeleteLabel(ctx context.Context, id, labelID int64) error { + l, err := GetLabelByID(ctx, labelID) if err != nil { if IsErrLabelNotExist(err) { return nil @@ -241,7 +241,7 @@ func DeleteLabel(id, labelID int64) error { return err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -289,9 +289,9 @@ func GetLabelByID(ctx context.Context, labelID int64) (*Label, error) { } // GetLabelsByIDs returns a list of labels by IDs -func GetLabelsByIDs(labelIDs []int64, cols ...string) ([]*Label, error) { +func GetLabelsByIDs(ctx context.Context, labelIDs []int64, cols ...string) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) - return labels, db.GetEngine(db.DefaultContext).Table("label"). + return labels, db.GetEngine(ctx).Table("label"). In("id", labelIDs). Asc("name"). Cols(cols...). @@ -339,9 +339,9 @@ func GetLabelInRepoByID(ctx context.Context, repoID, labelID int64) (*Label, err // GetLabelIDsInRepoByNames returns a list of labelIDs by names in a given // repository. // it silently ignores label names that do not belong to the repository. -func GetLabelIDsInRepoByNames(repoID int64, labelNames []string) ([]int64, error) { +func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []string) ([]int64, error) { labelIDs := make([]int64, 0, len(labelNames)) - return labelIDs, db.GetEngine(db.DefaultContext).Table("label"). + return labelIDs, db.GetEngine(ctx).Table("label"). Where("repo_id = ?", repoID). In("name", labelNames). Asc("name"). @@ -398,8 +398,8 @@ func GetLabelsByRepoID(ctx context.Context, repoID int64, sortType string, listO } // CountLabelsByRepoID count number of all labels that belong to given repository by ID. -func CountLabelsByRepoID(repoID int64) (int64, error) { - return db.GetEngine(db.DefaultContext).Where("repo_id = ?", repoID).Count(&Label{}) +func CountLabelsByRepoID(ctx context.Context, repoID int64) (int64, error) { + return db.GetEngine(ctx).Where("repo_id = ?", repoID).Count(&Label{}) } // GetLabelInOrgByName returns a label by name in given organization. @@ -442,13 +442,13 @@ func GetLabelInOrgByID(ctx context.Context, orgID, labelID int64) (*Label, error // GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given // organization. -func GetLabelIDsInOrgByNames(orgID int64, labelNames []string) ([]int64, error) { +func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) { if orgID <= 0 { return nil, ErrOrgLabelNotExist{0, orgID} } labelIDs := make([]int64, 0, len(labelNames)) - return labelIDs, db.GetEngine(db.DefaultContext).Table("label"). + return labelIDs, db.GetEngine(ctx).Table("label"). Where("org_id = ?", orgID). In("name", labelNames). Asc("name"). @@ -506,8 +506,8 @@ func GetLabelIDsByNames(ctx context.Context, labelNames []string) ([]int64, erro } // CountLabelsByOrgID count all labels that belong to given organization by ID. -func CountLabelsByOrgID(orgID int64) (int64, error) { - return db.GetEngine(db.DefaultContext).Where("org_id = ?", orgID).Count(&Label{}) +func CountLabelsByOrgID(ctx context.Context, orgID int64) (int64, error) { + return db.GetEngine(ctx).Where("org_id = ?", orgID).Count(&Label{}) } func updateLabelCols(ctx context.Context, l *Label, cols ...string) error { diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 3f0e980b31..3a8db6ceec 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -48,7 +48,7 @@ func TestNewLabels(t *testing.T) { for _, label := range labels { unittest.AssertNotExistsBean(t, label) } - assert.NoError(t, issues_model.NewLabels(labels...)) + assert.NoError(t, issues_model.NewLabels(db.DefaultContext, labels...)) for _, label := range labels { unittest.AssertExistsAndLoadBean(t, label, unittest.Cond("id = ?", label.ID)) } @@ -81,7 +81,7 @@ func TestGetLabelInRepoByName(t *testing.T) { func TestGetLabelInRepoByNames(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - labelIDs, err := issues_model.GetLabelIDsInRepoByNames(1, []string{"label1", "label2"}) + labelIDs, err := issues_model.GetLabelIDsInRepoByNames(db.DefaultContext, 1, []string{"label1", "label2"}) assert.NoError(t, err) assert.Len(t, labelIDs, 2) @@ -93,7 +93,7 @@ func TestGetLabelInRepoByNames(t *testing.T) { func TestGetLabelInRepoByNamesDiscardsNonExistentLabels(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // label3 doesn't exists.. See labels.yml - labelIDs, err := issues_model.GetLabelIDsInRepoByNames(1, []string{"label1", "label2", "label3"}) + labelIDs, err := issues_model.GetLabelIDsInRepoByNames(db.DefaultContext, 1, []string{"label1", "label2", "label3"}) assert.NoError(t, err) assert.Len(t, labelIDs, 2) @@ -166,7 +166,7 @@ func TestGetLabelInOrgByName(t *testing.T) { func TestGetLabelInOrgByNames(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - labelIDs, err := issues_model.GetLabelIDsInOrgByNames(3, []string{"orglabel3", "orglabel4"}) + labelIDs, err := issues_model.GetLabelIDsInOrgByNames(db.DefaultContext, 3, []string{"orglabel3", "orglabel4"}) assert.NoError(t, err) assert.Len(t, labelIDs, 2) @@ -178,7 +178,7 @@ func TestGetLabelInOrgByNames(t *testing.T) { func TestGetLabelInOrgByNamesDiscardsNonExistentLabels(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // orglabel99 doesn't exists.. See labels.yml - labelIDs, err := issues_model.GetLabelIDsInOrgByNames(3, []string{"orglabel3", "orglabel4", "orglabel99"}) + labelIDs, err := issues_model.GetLabelIDsInOrgByNames(db.DefaultContext, 3, []string{"orglabel3", "orglabel4", "orglabel99"}) assert.NoError(t, err) assert.Len(t, labelIDs, 2) @@ -269,7 +269,7 @@ func TestUpdateLabel(t *testing.T) { } label.Color = update.Color label.Name = update.Name - assert.NoError(t, issues_model.UpdateLabel(update)) + assert.NoError(t, issues_model.UpdateLabel(db.DefaultContext, update)) newLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) assert.EqualValues(t, label.ID, newLabel.ID) assert.EqualValues(t, label.Color, newLabel.Color) @@ -282,13 +282,13 @@ func TestUpdateLabel(t *testing.T) { func TestDeleteLabel(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - assert.NoError(t, issues_model.DeleteLabel(label.RepoID, label.ID)) + assert.NoError(t, issues_model.DeleteLabel(db.DefaultContext, label.RepoID, label.ID)) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: label.ID, RepoID: label.RepoID}) - assert.NoError(t, issues_model.DeleteLabel(label.RepoID, label.ID)) + assert.NoError(t, issues_model.DeleteLabel(db.DefaultContext, label.RepoID, label.ID)) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: label.ID}) - assert.NoError(t, issues_model.DeleteLabel(unittest.NonexistentID, unittest.NonexistentID)) + assert.NoError(t, issues_model.DeleteLabel(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)) unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) } @@ -307,7 +307,7 @@ func TestNewIssueLabel(t *testing.T) { // add new IssueLabel prevNumIssues := label.NumIssues - assert.NoError(t, issues_model.NewIssueLabel(issue, label, doer)) + assert.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, label, doer)) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ Type: issues_model.CommentTypeLabel, @@ -320,7 +320,7 @@ func TestNewIssueLabel(t *testing.T) { assert.EqualValues(t, prevNumIssues+1, label.NumIssues) // re-add existing IssueLabel - assert.NoError(t, issues_model.NewIssueLabel(issue, label, doer)) + assert.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, label, doer)) unittest.CheckConsistencyFor(t, &issues_model.Issue{}, &issues_model.Label{}) } @@ -334,19 +334,19 @@ func TestNewIssueExclusiveLabel(t *testing.T) { exclusiveLabelB := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 8}) // coexisting regular and exclusive label - assert.NoError(t, issues_model.NewIssueLabel(issue, otherLabel, doer)) - assert.NoError(t, issues_model.NewIssueLabel(issue, exclusiveLabelA, doer)) + assert.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, otherLabel, doer)) + assert.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, exclusiveLabelA, doer)) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: otherLabel.ID}) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: exclusiveLabelA.ID}) // exclusive label replaces existing one - assert.NoError(t, issues_model.NewIssueLabel(issue, exclusiveLabelB, doer)) + assert.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, exclusiveLabelB, doer)) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: otherLabel.ID}) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: exclusiveLabelB.ID}) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: exclusiveLabelA.ID}) // exclusive label replaces existing one again - assert.NoError(t, issues_model.NewIssueLabel(issue, exclusiveLabelA, doer)) + assert.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, exclusiveLabelA, doer)) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: otherLabel.ID}) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: exclusiveLabelA.ID}) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: exclusiveLabelB.ID}) @@ -359,7 +359,7 @@ func TestNewIssueLabels(t *testing.T) { issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 5}) doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - assert.NoError(t, issues_model.NewIssueLabels(issue, []*issues_model.Label{label1, label2}, doer)) + assert.NoError(t, issues_model.NewIssueLabels(db.DefaultContext, issue, []*issues_model.Label{label1, label2}, doer)) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label1.ID}) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ Type: issues_model.CommentTypeLabel, @@ -377,7 +377,7 @@ func TestNewIssueLabels(t *testing.T) { assert.EqualValues(t, 1, label2.NumClosedIssues) // corner case: test empty slice - assert.NoError(t, issues_model.NewIssueLabels(issue, []*issues_model.Label{}, doer)) + assert.NoError(t, issues_model.NewIssueLabels(db.DefaultContext, issue, []*issues_model.Label{}, doer)) unittest.CheckConsistencyFor(t, &issues_model.Issue{}, &issues_model.Label{}) } diff --git a/models/issues/main_test.go b/models/issues/main_test.go index 190df027f4..ba83ca5552 100644 --- a/models/issues/main_test.go +++ b/models/issues/main_test.go @@ -4,7 +4,6 @@ package issues_test import ( - "path/filepath" "testing" issues_model "code.gitea.io/gitea/models/issues" @@ -30,7 +29,5 @@ func TestFixturesAreConsistent(t *testing.T) { } func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/issues/milestone.go b/models/issues/milestone.go index c15b2a41fe..ad1d5d0453 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -103,8 +103,8 @@ func (m *Milestone) State() api.StateType { } // NewMilestone creates new milestone of repository. -func NewMilestone(m *Milestone) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func NewMilestone(ctx context.Context, m *Milestone) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -140,9 +140,9 @@ func GetMilestoneByRepoID(ctx context.Context, repoID, id int64) (*Milestone, er } // GetMilestoneByRepoIDANDName return a milestone if one exist by name and repo -func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) { +func GetMilestoneByRepoIDANDName(ctx context.Context, repoID int64, name string) (*Milestone, error) { var mile Milestone - has, err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND name=?", repoID, name).Get(&mile) + has, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, name).Get(&mile) if err != nil { return nil, err } @@ -153,8 +153,8 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) } // UpdateMilestone updates information of given milestone. -func UpdateMilestone(m *Milestone, oldIsClosed bool) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateMilestone(ctx context.Context, m *Milestone, oldIsClosed bool) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -211,8 +211,8 @@ func UpdateMilestoneCounters(ctx context.Context, id int64) error { } // ChangeMilestoneStatusByRepoIDAndID changes a milestone open/closed status if the milestone ID is in the repo. -func ChangeMilestoneStatusByRepoIDAndID(repoID, milestoneID int64, isClosed bool) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeMilestoneStatusByRepoIDAndID(ctx context.Context, repoID, milestoneID int64, isClosed bool) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -238,8 +238,8 @@ func ChangeMilestoneStatusByRepoIDAndID(repoID, milestoneID int64, isClosed bool } // ChangeMilestoneStatus changes the milestone open/closed status. -func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -269,8 +269,8 @@ func changeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) err } // DeleteMilestoneByRepoID deletes a milestone from a repository. -func DeleteMilestoneByRepoID(repoID, id int64) error { - m, err := GetMilestoneByRepoID(db.DefaultContext, repoID, id) +func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error { + m, err := GetMilestoneByRepoID(ctx, repoID, id) if err != nil { if IsErrMilestoneNotExist(err) { return nil @@ -278,12 +278,12 @@ func DeleteMilestoneByRepoID(repoID, id int64) error { return err } - repo, err := repo_model.GetRepositoryByID(db.DefaultContext, m.RepoID) + repo, err := repo_model.GetRepositoryByID(ctx, m.RepoID) if err != nil { return err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -332,7 +332,8 @@ func updateRepoMilestoneNum(ctx context.Context, repoID int64) error { return err } -func (m *Milestone) loadTotalTrackedTime(ctx context.Context) error { +// LoadTotalTrackedTime loads the tracked time for the milestone +func (m *Milestone) LoadTotalTrackedTime(ctx context.Context) error { type totalTimesByMilestone struct { MilestoneID int64 Time int64 @@ -355,18 +356,13 @@ func (m *Milestone) loadTotalTrackedTime(ctx context.Context) error { return nil } -// LoadTotalTrackedTime loads the tracked time for the milestone -func (m *Milestone) LoadTotalTrackedTime() error { - return m.loadTotalTrackedTime(db.DefaultContext) -} - // InsertMilestones creates milestones of repository. -func InsertMilestones(ms ...*Milestone) (err error) { +func InsertMilestones(ctx context.Context, ms ...*Milestone) (err error) { if len(ms) == 0 { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/milestone_list.go b/models/issues/milestone_list.go index b0c29106a0..d55c18a995 100644 --- a/models/issues/milestone_list.go +++ b/models/issues/milestone_list.go @@ -58,8 +58,8 @@ func (opts GetMilestonesOption) toCond() builder.Cond { } // GetMilestones returns milestones filtered by GetMilestonesOption's -func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) { - sess := db.GetEngine(db.DefaultContext).Where(opts.toCond()) +func GetMilestones(ctx context.Context, opts GetMilestonesOption) (MilestoneList, int64, error) { + sess := db.GetEngine(ctx).Where(opts.toCond()) if opts.Page != 0 { sess = db.SetSessionPagination(sess, &opts) @@ -100,9 +100,9 @@ func GetMilestoneIDsByNames(ctx context.Context, names []string) ([]int64, error } // SearchMilestones search milestones -func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType, keyword string) (MilestoneList, error) { +func SearchMilestones(ctx context.Context, repoCond builder.Cond, page int, isClosed bool, sortType, keyword string) (MilestoneList, error) { miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) - sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) + sess := db.GetEngine(ctx).Where("is_closed = ?", isClosed) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } @@ -131,8 +131,9 @@ func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType, } // GetMilestonesByRepoIDs returns a list of milestones of given repositories and status. -func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) { +func GetMilestonesByRepoIDs(ctx context.Context, repoIDs []int64, page int, isClosed bool, sortType string) (MilestoneList, error) { return SearchMilestones( + ctx, builder.In("repo_id", repoIDs), page, isClosed, @@ -141,7 +142,8 @@ func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType s ) } -func (milestones MilestoneList) loadTotalTrackedTimes(ctx context.Context) error { +// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request +func (milestones MilestoneList) LoadTotalTrackedTimes(ctx context.Context) error { type totalTimesByMilestone struct { MilestoneID int64 Time int64 @@ -181,11 +183,6 @@ func (milestones MilestoneList) loadTotalTrackedTimes(ctx context.Context) error return nil } -// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request -func (milestones MilestoneList) LoadTotalTrackedTimes() error { - return milestones.loadTotalTrackedTimes(db.DefaultContext) -} - // CountMilestones returns number of milestones in given repository with other options func CountMilestones(ctx context.Context, opts GetMilestonesOption) (int64, error) { return db.GetEngine(ctx). @@ -194,8 +191,8 @@ func CountMilestones(ctx context.Context, opts GetMilestonesOption) (int64, erro } // CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options` -func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { - sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) +func CountMilestonesByRepoCond(ctx context.Context, repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { + sess := db.GetEngine(ctx).Where("is_closed = ?", isClosed) if repoCond.IsValid() { sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) } @@ -219,8 +216,8 @@ func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64] } // CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options` -func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) { - sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", isClosed) +func CountMilestonesByRepoCondAndKw(ctx context.Context, repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) { + sess := db.GetEngine(ctx).Where("is_closed = ?", isClosed) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } @@ -257,11 +254,11 @@ func (m MilestonesStats) Total() int64 { } // GetMilestonesStatsByRepoCond returns milestone statistic information for dashboard by given conditions. -func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, error) { +func GetMilestonesStatsByRepoCond(ctx context.Context, repoCond builder.Cond) (*MilestonesStats, error) { var err error stats := &MilestonesStats{} - sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false) + sess := db.GetEngine(ctx).Where("is_closed = ?", false) if repoCond.IsValid() { sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) } @@ -270,7 +267,7 @@ func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, erro return nil, err } - sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true) + sess = db.GetEngine(ctx).Where("is_closed = ?", true) if repoCond.IsValid() { sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) } @@ -283,11 +280,11 @@ func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, erro } // GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword. -func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (*MilestonesStats, error) { +func GetMilestonesStatsByRepoCondAndKw(ctx context.Context, repoCond builder.Cond, keyword string) (*MilestonesStats, error) { var err error stats := &MilestonesStats{} - sess := db.GetEngine(db.DefaultContext).Where("is_closed = ?", false) + sess := db.GetEngine(ctx).Where("is_closed = ?", false) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } @@ -299,7 +296,7 @@ func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (* return nil, err } - sess = db.GetEngine(db.DefaultContext).Where("is_closed = ?", true) + sess = db.GetEngine(ctx).Where("is_closed = ?", true) if len(keyword) > 0 { sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) } diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go index e85d77ebc8..ecfccf6ddb 100644 --- a/models/issues/milestone_test.go +++ b/models/issues/milestone_test.go @@ -40,7 +40,7 @@ func TestGetMilestonesByRepoID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(repoID int64, state api.StateType) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{ + milestones, _, err := issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{ RepoID: repo.ID, State: state, }) @@ -77,7 +77,7 @@ func TestGetMilestonesByRepoID(t *testing.T) { test(3, api.StateClosed) test(3, api.StateAll) - milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{ + milestones, _, err := issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{ RepoID: unittest.NonexistentID, State: api.StateOpen, }) @@ -90,7 +90,7 @@ func TestGetMilestones(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) test := func(sortType string, sortCond func(*issues_model.Milestone) int) { for _, page := range []int{0, 1} { - milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{ + milestones, _, err := issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{ ListOptions: db.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, @@ -107,7 +107,7 @@ func TestGetMilestones(t *testing.T) { } assert.True(t, sort.IntsAreSorted(values)) - milestones, _, err = issues_model.GetMilestones(issues_model.GetMilestonesOption{ + milestones, _, err = issues_model.GetMilestones(db.DefaultContext, issues_model.GetMilestonesOption{ ListOptions: db.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, @@ -201,12 +201,12 @@ func TestCountMilestonesByRepoIDs(t *testing.T) { repo1OpenCount, repo1ClosedCount := milestonesCount(1) repo2OpenCount, repo2ClosedCount := milestonesCount(2) - openCounts, err := issues_model.CountMilestonesByRepoCond(builder.In("repo_id", []int64{1, 2}), false) + openCounts, err := issues_model.CountMilestonesByRepoCond(db.DefaultContext, builder.In("repo_id", []int64{1, 2}), false) assert.NoError(t, err) assert.EqualValues(t, repo1OpenCount, openCounts[1]) assert.EqualValues(t, repo2OpenCount, openCounts[2]) - closedCounts, err := issues_model.CountMilestonesByRepoCond(builder.In("repo_id", []int64{1, 2}), true) + closedCounts, err := issues_model.CountMilestonesByRepoCond(db.DefaultContext, builder.In("repo_id", []int64{1, 2}), true) assert.NoError(t, err) assert.EqualValues(t, repo1ClosedCount, closedCounts[1]) assert.EqualValues(t, repo2ClosedCount, closedCounts[2]) @@ -218,7 +218,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) { repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) test := func(sortType string, sortCond func(*issues_model.Milestone) int) { for _, page := range []int{0, 1} { - openMilestones, err := issues_model.GetMilestonesByRepoIDs([]int64{repo1.ID, repo2.ID}, page, false, sortType) + openMilestones, err := issues_model.GetMilestonesByRepoIDs(db.DefaultContext, []int64{repo1.ID, repo2.ID}, page, false, sortType) assert.NoError(t, err) assert.Len(t, openMilestones, repo1.NumOpenMilestones+repo2.NumOpenMilestones) values := make([]int, len(openMilestones)) @@ -227,7 +227,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) { } assert.True(t, sort.IntsAreSorted(values)) - closedMilestones, err := issues_model.GetMilestonesByRepoIDs([]int64{repo1.ID, repo2.ID}, page, true, sortType) + closedMilestones, err := issues_model.GetMilestonesByRepoIDs(db.DefaultContext, []int64{repo1.ID, repo2.ID}, page, true, sortType) assert.NoError(t, err) assert.Len(t, closedMilestones, repo1.NumClosedMilestones+repo2.NumClosedMilestones) values = make([]int, len(closedMilestones)) @@ -262,7 +262,7 @@ func TestGetMilestonesStats(t *testing.T) { test := func(repoID int64) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - stats, err := issues_model.GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": repoID})) + stats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.And(builder.Eq{"repo_id": repoID})) assert.NoError(t, err) assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, stats.OpenCount) assert.EqualValues(t, repo.NumClosedMilestones, stats.ClosedCount) @@ -271,7 +271,7 @@ func TestGetMilestonesStats(t *testing.T) { test(2) test(3) - stats, err := issues_model.GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": unittest.NonexistentID})) + stats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.And(builder.Eq{"repo_id": unittest.NonexistentID})) assert.NoError(t, err) assert.EqualValues(t, 0, stats.OpenCount) assert.EqualValues(t, 0, stats.ClosedCount) @@ -279,7 +279,7 @@ func TestGetMilestonesStats(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - milestoneStats, err := issues_model.GetMilestonesStatsByRepoCond(builder.In("repo_id", []int64{repo1.ID, repo2.ID})) + milestoneStats, err := issues_model.GetMilestonesStatsByRepoCond(db.DefaultContext, builder.In("repo_id", []int64{repo1.ID, repo2.ID})) assert.NoError(t, err) assert.EqualValues(t, repo1.NumOpenMilestones+repo2.NumOpenMilestones, milestoneStats.OpenCount) assert.EqualValues(t, repo1.NumClosedMilestones+repo2.NumClosedMilestones, milestoneStats.ClosedCount) @@ -293,7 +293,7 @@ func TestNewMilestone(t *testing.T) { Content: "milestoneContent", } - assert.NoError(t, issues_model.NewMilestone(milestone)) + assert.NoError(t, issues_model.NewMilestone(db.DefaultContext, milestone)) unittest.AssertExistsAndLoadBean(t, milestone) unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{}) } @@ -302,22 +302,22 @@ func TestChangeMilestoneStatus(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - assert.NoError(t, issues_model.ChangeMilestoneStatus(milestone, true)) + assert.NoError(t, issues_model.ChangeMilestoneStatus(db.DefaultContext, milestone, true)) unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}, "is_closed=1") unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{}) - assert.NoError(t, issues_model.ChangeMilestoneStatus(milestone, false)) + assert.NoError(t, issues_model.ChangeMilestoneStatus(db.DefaultContext, milestone, false)) unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}, "is_closed=0") unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{}) } func TestDeleteMilestoneByRepoID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, issues_model.DeleteMilestoneByRepoID(1, 1)) + assert.NoError(t, issues_model.DeleteMilestoneByRepoID(db.DefaultContext, 1, 1)) unittest.AssertNotExistsBean(t, &issues_model.Milestone{ID: 1}) unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: 1}) - assert.NoError(t, issues_model.DeleteMilestoneByRepoID(unittest.NonexistentID, unittest.NonexistentID)) + assert.NoError(t, issues_model.DeleteMilestoneByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)) } func TestUpdateMilestone(t *testing.T) { @@ -326,7 +326,7 @@ func TestUpdateMilestone(t *testing.T) { milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) milestone.Name = " newMilestoneName " milestone.Content = "newMilestoneContent" - assert.NoError(t, issues_model.UpdateMilestone(milestone, milestone.IsClosed)) + assert.NoError(t, issues_model.UpdateMilestone(db.DefaultContext, milestone, milestone.IsClosed)) milestone = unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) assert.EqualValues(t, "newMilestoneName", milestone.Name) unittest.CheckConsistencyFor(t, &issues_model.Milestone{}) @@ -361,7 +361,7 @@ func TestMigrate_InsertMilestones(t *testing.T) { RepoID: repo.ID, Name: name, } - err := issues_model.InsertMilestones(ms) + err := issues_model.InsertMilestones(db.DefaultContext, ms) assert.NoError(t, err) unittest.AssertExistsAndLoadBean(t, ms) repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) diff --git a/models/issues/pull.go b/models/issues/pull.go index 1c163ecca4..2379c61976 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -272,7 +272,7 @@ func (pr *PullRequest) LoadAttributes(ctx context.Context) (err error) { if pr.HasMerged && pr.Merger == nil { pr.Merger, err = user_model.GetUserByID(ctx, pr.MergerID) if user_model.IsErrUserNotExist(err) { - pr.MergerID = -1 + pr.MergerID = user_model.GhostUserID pr.Merger = user_model.NewGhostUser() } else if err != nil { return fmt.Errorf("getUserByID [%d]: %w", pr.MergerID, err) @@ -311,7 +311,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error { return nil } - reviews, err := GetReviewsByIssueID(pr.Issue.ID) + reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID) if err != nil { return err } @@ -378,9 +378,9 @@ func (pr *PullRequest) GetApprovalCounts(ctx context.Context) ([]*ReviewCount, e } // GetApprovers returns the approvers of the pull request -func (pr *PullRequest) GetApprovers() string { +func (pr *PullRequest) GetApprovers(ctx context.Context) string { stringBuilder := strings.Builder{} - if err := pr.getReviewedByLines(&stringBuilder); err != nil { + if err := pr.getReviewedByLines(ctx, &stringBuilder); err != nil { log.Error("Unable to getReviewedByLines: Error: %v", err) return "" } @@ -388,14 +388,14 @@ func (pr *PullRequest) GetApprovers() string { return stringBuilder.String() } -func (pr *PullRequest) getReviewedByLines(writer io.Writer) error { +func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer) error { maxReviewers := setting.Repository.PullRequest.DefaultMergeMessageMaxApprovers if maxReviewers == 0 { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -594,9 +594,9 @@ func GetUnmergedPullRequest(ctx context.Context, headRepoID, baseRepoID int64, h // GetLatestPullRequestByHeadInfo returns the latest pull request (regardless of its status) // by given head information (repo and branch). -func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, error) { +func GetLatestPullRequestByHeadInfo(ctx context.Context, repoID int64, branch string) (*PullRequest, error) { pr := new(PullRequest) - has, err := db.GetEngine(db.DefaultContext). + has, err := db.GetEngine(ctx). Where("head_repo_id = ? AND head_branch = ? AND flow = ?", repoID, branch, PullRequestFlowGithub). OrderBy("id DESC"). Get(pr) @@ -646,9 +646,9 @@ func GetPullRequestByID(ctx context.Context, id int64) (*PullRequest, error) { } // GetPullRequestByIssueIDWithNoAttributes returns pull request with no attributes loaded by given issue ID. -func GetPullRequestByIssueIDWithNoAttributes(issueID int64) (*PullRequest, error) { +func GetPullRequestByIssueIDWithNoAttributes(ctx context.Context, issueID int64) (*PullRequest, error) { var pr PullRequest - has, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", issueID).Get(&pr) + has, err := db.GetEngine(ctx).Where("issue_id = ?", issueID).Get(&pr) if err != nil { return nil, err } @@ -687,14 +687,14 @@ func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*P } // Update updates all fields of pull request. -func (pr *PullRequest) Update() error { - _, err := db.GetEngine(db.DefaultContext).ID(pr.ID).AllCols().Update(pr) +func (pr *PullRequest) Update(ctx context.Context) error { + _, err := db.GetEngine(ctx).ID(pr.ID).AllCols().Update(pr) return err } // UpdateCols updates specific fields of pull request. -func (pr *PullRequest) UpdateCols(cols ...string) error { - _, err := db.GetEngine(db.DefaultContext).ID(pr.ID).Cols(cols...).Update(pr) +func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error { + _, err := db.GetEngine(ctx).ID(pr.ID).Cols(cols...).Update(pr) return err } @@ -706,8 +706,8 @@ func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string // IsWorkInProgress determine if the Pull Request is a Work In Progress by its title // Issue must be set before this method can be called. -func (pr *PullRequest) IsWorkInProgress() bool { - if err := pr.LoadIssue(db.DefaultContext); err != nil { +func (pr *PullRequest) IsWorkInProgress(ctx context.Context) bool { + if err := pr.LoadIssue(ctx); err != nil { log.Error("LoadIssue: %v", err) return false } @@ -774,8 +774,8 @@ func GetPullRequestsByHeadBranch(ctx context.Context, headBranch string, headRep } // GetBaseBranchLink returns the relative URL of the base branch -func (pr *PullRequest) GetBaseBranchLink() string { - if err := pr.LoadBaseRepo(db.DefaultContext); err != nil { +func (pr *PullRequest) GetBaseBranchLink(ctx context.Context) string { + if err := pr.LoadBaseRepo(ctx); err != nil { log.Error("LoadBaseRepo: %v", err) return "" } @@ -786,12 +786,12 @@ func (pr *PullRequest) GetBaseBranchLink() string { } // GetHeadBranchLink returns the relative URL of the head branch -func (pr *PullRequest) GetHeadBranchLink() string { +func (pr *PullRequest) GetHeadBranchLink(ctx context.Context) string { if pr.Flow == PullRequestFlowAGit { return "" } - if err := pr.LoadHeadRepo(db.DefaultContext); err != nil { + if err := pr.LoadHeadRepo(ctx); err != nil { log.Error("LoadHeadRepo: %v", err) return "" } @@ -810,14 +810,14 @@ func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error { } // Mergeable returns if the pullrequest is mergeable. -func (pr *PullRequest) Mergeable() bool { +func (pr *PullRequest) Mergeable(ctx context.Context) bool { // If a pull request isn't mergable if it's: // - Being conflict checked. // - Has a conflict. // - Received a error while being conflict checked. // - Is a work-in-progress pull request. return pr.Status != PullRequestStatusChecking && pr.Status != PullRequestStatusConflict && - pr.Status != PullRequestStatusError && !pr.IsWorkInProgress() + pr.Status != PullRequestStatusError && !pr.IsWorkInProgress(ctx) } // HasEnoughApprovals returns true if pr has enough granted approvals. @@ -890,7 +890,7 @@ func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr * func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullRequest) error { files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"} - if pr.IsWorkInProgress() { + if pr.IsWorkInProgress(ctx) { return nil } @@ -1040,7 +1040,7 @@ func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, warnings = append(warnings, fmt.Sprintf("incorrect codeowner organization: %s", user)) continue } - teams, err := org.LoadTeams() + teams, err := org.LoadTeams(ctx) if err != nil { warnings = append(warnings, fmt.Sprintf("incorrect codeowner team: %s", user)) continue diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index c4506ef150..c209386e2e 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -27,8 +27,8 @@ type PullRequestsOptions struct { MilestoneID int64 } -func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xorm.Session, error) { - sess := db.GetEngine(db.DefaultContext).Where("pull_request.base_repo_id=?", baseRepoID) +func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) (*xorm.Session, error) { + sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID) sess.Join("INNER", "issue", "pull_request.issue_id = issue.id") switch opts.State { @@ -115,21 +115,21 @@ func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch } // GetPullRequestIDsByCheckStatus returns all pull requests according the special checking status. -func GetPullRequestIDsByCheckStatus(status PullRequestStatus) ([]int64, error) { +func GetPullRequestIDsByCheckStatus(ctx context.Context, status PullRequestStatus) ([]int64, error) { prs := make([]int64, 0, 10) - return prs, db.GetEngine(db.DefaultContext).Table("pull_request"). + return prs, db.GetEngine(ctx).Table("pull_request"). Where("status=?", status). Cols("pull_request.id"). Find(&prs) } // PullRequests returns all pull requests for a base Repo by the given conditions -func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, int64, error) { +func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, int64, error) { if opts.Page <= 0 { opts.Page = 1 } - countSession, err := listPullRequestStatement(baseRepoID, opts) + countSession, err := listPullRequestStatement(ctx, baseRepoID, opts) if err != nil { log.Error("listPullRequestStatement: %v", err) return nil, 0, err @@ -140,7 +140,7 @@ func PullRequests(baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, return nil, maxResults, err } - findSession, err := listPullRequestStatement(baseRepoID, opts) + findSession, err := listPullRequestStatement(ctx, baseRepoID, opts) applySorts(findSession, opts.SortType, 0) if err != nil { log.Error("listPullRequestStatement: %v", err) diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 83977560ae..173417136c 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -60,7 +60,7 @@ func TestPullRequest_LoadHeadRepo(t *testing.T) { func TestPullRequestsNewest(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs, count, err := issues_model.PullRequests(1, &issues_model.PullRequestsOptions{ + prs, count, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{ ListOptions: db.ListOptions{ Page: 1, }, @@ -107,7 +107,7 @@ func TestLoadRequestedReviewers(t *testing.T) { func TestPullRequestsOldest(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs, count, err := issues_model.PullRequests(1, &issues_model.PullRequestsOptions{ + prs, count, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{ ListOptions: db.ListOptions{ Page: 1, }, @@ -213,7 +213,7 @@ func TestPullRequest_Update(t *testing.T) { pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}) pr.BaseBranch = "baseBranch" pr.HeadBranch = "headBranch" - pr.Update() + pr.Update(db.DefaultContext) pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) assert.Equal(t, "baseBranch", pr.BaseBranch) @@ -228,7 +228,7 @@ func TestPullRequest_UpdateCols(t *testing.T) { BaseBranch: "baseBranch", HeadBranch: "headBranch", } - assert.NoError(t, pr.UpdateCols("head_branch")) + assert.NoError(t, pr.UpdateCols(db.DefaultContext, "head_branch")) pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}) assert.Equal(t, "master", pr.BaseBranch) @@ -260,13 +260,13 @@ func TestPullRequest_IsWorkInProgress(t *testing.T) { pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) pr.LoadIssue(db.DefaultContext) - assert.False(t, pr.IsWorkInProgress()) + assert.False(t, pr.IsWorkInProgress(db.DefaultContext)) pr.Issue.Title = "WIP: " + pr.Issue.Title - assert.True(t, pr.IsWorkInProgress()) + assert.True(t, pr.IsWorkInProgress(db.DefaultContext)) pr.Issue.Title = "[wip]: " + pr.Issue.Title - assert.True(t, pr.IsWorkInProgress()) + assert.True(t, pr.IsWorkInProgress(db.DefaultContext)) } func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) { @@ -317,9 +317,9 @@ func TestParseCodeOwnersLine(t *testing.T) { {Line: "# comment", Tokens: []string{}}, {Line: "!.* @user1 @org1/team1", Tokens: []string{"!.*", "@user1", "@org1/team1"}}, {Line: `.*\\.js @user2 #comment`, Tokens: []string{`.*\.js`, "@user2"}}, - {Line: `docs/(aws|google|azure)/[^/]*\\.(md|txt) @user3 @org2/team2`, Tokens: []string{`docs/(aws|google|azure)/[^/]*\.(md|txt)`, "@user3", "@org2/team2"}}, - {Line: `\#path @user3`, Tokens: []string{`#path`, "@user3"}}, - {Line: `path\ with\ spaces/ @user3`, Tokens: []string{`path with spaces/`, "@user3"}}, + {Line: `docs/(aws|google|azure)/[^/]*\\.(md|txt) @org3 @org2/team2`, Tokens: []string{`docs/(aws|google|azure)/[^/]*\.(md|txt)`, "@org3", "@org2/team2"}}, + {Line: `\#path @org3`, Tokens: []string{`#path`, "@org3"}}, + {Line: `path\ with\ spaces/ @org3`, Tokens: []string{`path with spaces/`, "@org3"}}, } for _, g := range given { @@ -334,8 +334,8 @@ func TestGetApprovers(t *testing.T) { // Official reviews are already deduplicated. Allow unofficial reviews // to assert that there are no duplicated approvers. setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly = false - approvers := pr.GetApprovers() - expected := "Reviewed-by: User Five \nReviewed-by: User Six \n" + approvers := pr.GetApprovers(db.DefaultContext) + expected := "Reviewed-by: User Five \nReviewed-by: Org Six \n" assert.EqualValues(t, expected, approvers) } diff --git a/models/issues/reaction.go b/models/issues/reaction.go index 28da696366..bb47cf24ca 100644 --- a/models/issues/reaction.go +++ b/models/issues/reaction.go @@ -71,11 +71,11 @@ type Reaction struct { } // LoadUser load user of reaction -func (r *Reaction) LoadUser() (*user_model.User, error) { +func (r *Reaction) LoadUser(ctx context.Context) (*user_model.User, error) { if r.User != nil { return r.User, nil } - user, err := user_model.GetUserByID(db.DefaultContext, r.UserID) + user, err := user_model.GetUserByID(ctx, r.UserID) if err != nil { return nil, err } @@ -141,16 +141,16 @@ func (opts *FindReactionsOptions) toConds() builder.Cond { } // FindCommentReactions returns a ReactionList of all reactions from an comment -func FindCommentReactions(issueID, commentID int64) (ReactionList, int64, error) { - return FindReactions(db.DefaultContext, FindReactionsOptions{ +func FindCommentReactions(ctx context.Context, issueID, commentID int64) (ReactionList, int64, error) { + return FindReactions(ctx, FindReactionsOptions{ IssueID: issueID, CommentID: commentID, }) } // FindIssueReactions returns a ReactionList of all reactions from an issue -func FindIssueReactions(issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) { - return FindReactions(db.DefaultContext, FindReactionsOptions{ +func FindIssueReactions(ctx context.Context, issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) { + return FindReactions(ctx, FindReactionsOptions{ ListOptions: listOptions, IssueID: issueID, CommentID: -1, @@ -218,12 +218,12 @@ type ReactionOptions struct { } // CreateReaction creates reaction for issue or comment. -func CreateReaction(opts *ReactionOptions) (*Reaction, error) { +func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) { if !setting.UI.ReactionsLookup.Contains(opts.Type) { return nil, ErrForbiddenIssueReaction{opts.Type} } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -241,8 +241,8 @@ func CreateReaction(opts *ReactionOptions) (*Reaction, error) { } // CreateIssueReaction creates a reaction on issue. -func CreateIssueReaction(doerID, issueID int64, content string) (*Reaction, error) { - return CreateReaction(&ReactionOptions{ +func CreateIssueReaction(ctx context.Context, doerID, issueID int64, content string) (*Reaction, error) { + return CreateReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, @@ -250,8 +250,8 @@ func CreateIssueReaction(doerID, issueID int64, content string) (*Reaction, erro } // CreateCommentReaction creates a reaction on comment. -func CreateCommentReaction(doerID, issueID, commentID int64, content string) (*Reaction, error) { - return CreateReaction(&ReactionOptions{ +func CreateCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) (*Reaction, error) { + return CreateReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, @@ -279,8 +279,8 @@ func DeleteReaction(ctx context.Context, opts *ReactionOptions) error { } // DeleteIssueReaction deletes a reaction on issue. -func DeleteIssueReaction(doerID, issueID int64, content string) error { - return DeleteReaction(db.DefaultContext, &ReactionOptions{ +func DeleteIssueReaction(ctx context.Context, doerID, issueID int64, content string) error { + return DeleteReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, @@ -289,8 +289,8 @@ func DeleteIssueReaction(doerID, issueID int64, content string) error { } // DeleteCommentReaction deletes a reaction on comment. -func DeleteCommentReaction(doerID, issueID, commentID int64, content string) error { - return DeleteReaction(db.DefaultContext, &ReactionOptions{ +func DeleteCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) error { + return DeleteReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, diff --git a/models/issues/reaction_test.go b/models/issues/reaction_test.go index e397568ac0..5dc8e1a5f3 100644 --- a/models/issues/reaction_test.go +++ b/models/issues/reaction_test.go @@ -20,9 +20,9 @@ func addReaction(t *testing.T, doerID, issueID, commentID int64, content string) var reaction *issues_model.Reaction var err error if commentID == 0 { - reaction, err = issues_model.CreateIssueReaction(doerID, issueID, content) + reaction, err = issues_model.CreateIssueReaction(db.DefaultContext, doerID, issueID, content) } else { - reaction, err = issues_model.CreateCommentReaction(doerID, issueID, commentID, content) + reaction, err = issues_model.CreateCommentReaction(db.DefaultContext, doerID, issueID, commentID, content) } assert.NoError(t, err) assert.NotNil(t, reaction) @@ -49,7 +49,7 @@ func TestIssueAddDuplicateReaction(t *testing.T) { addReaction(t, user1.ID, issue1ID, 0, "heart") - reaction, err := issues_model.CreateReaction(&issues_model.ReactionOptions{ + reaction, err := issues_model.CreateReaction(db.DefaultContext, &issues_model.ReactionOptions{ DoerID: user1.ID, IssueID: issue1ID, Type: "heart", @@ -70,7 +70,7 @@ func TestIssueDeleteReaction(t *testing.T) { addReaction(t, user1.ID, issue1ID, 0, "heart") - err := issues_model.DeleteIssueReaction(user1.ID, issue1ID, "heart") + err := issues_model.DeleteIssueReaction(db.DefaultContext, user1.ID, issue1ID, "heart") assert.NoError(t, err) unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID}) @@ -83,7 +83,7 @@ func TestIssueReactionCount(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) ghost := user_model.NewGhostUser() @@ -92,8 +92,8 @@ func TestIssueReactionCount(t *testing.T) { addReaction(t, user1.ID, issueID, 0, "heart") addReaction(t, user2.ID, issueID, 0, "heart") - addReaction(t, user3.ID, issueID, 0, "heart") - addReaction(t, user3.ID, issueID, 0, "+1") + addReaction(t, org3.ID, issueID, 0, "heart") + addReaction(t, org3.ID, issueID, 0, "+1") addReaction(t, user4.ID, issueID, 0, "+1") addReaction(t, user4.ID, issueID, 0, "heart") addReaction(t, ghost.ID, issueID, 0, "-1") @@ -136,7 +136,7 @@ func TestIssueCommentDeleteReaction(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) var issue1ID int64 = 1 @@ -144,7 +144,7 @@ func TestIssueCommentDeleteReaction(t *testing.T) { addReaction(t, user1.ID, issue1ID, comment1ID, "heart") addReaction(t, user2.ID, issue1ID, comment1ID, "heart") - addReaction(t, user3.ID, issue1ID, comment1ID, "heart") + addReaction(t, org3.ID, issue1ID, comment1ID, "heart") addReaction(t, user4.ID, issue1ID, comment1ID, "+1") reactionsList, _, err := issues_model.FindReactions(db.DefaultContext, issues_model.FindReactionsOptions{ @@ -168,7 +168,7 @@ func TestIssueCommentReactionCount(t *testing.T) { var comment1ID int64 = 1 addReaction(t, user1.ID, issue1ID, comment1ID, "heart") - assert.NoError(t, issues_model.DeleteCommentReaction(user1.ID, issue1ID, comment1ID, "heart")) + assert.NoError(t, issues_model.DeleteCommentReaction(db.DefaultContext, user1.ID, issue1ID, comment1ID, "heart")) unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID}) } diff --git a/models/issues/review.go b/models/issues/review.go index 931f1a2ba7..8b64ba8048 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -231,7 +231,7 @@ type CreateReviewOptions struct { } // IsOfficialReviewer check if at least one of the provided reviewers can make official reviews in issue (counts towards required approvals) -func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_model.User) (bool, error) { +func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewer *user_model.User) (bool, error) { pr, err := GetPullRequestByIssueID(ctx, issue.ID) if err != nil { return false, err @@ -242,14 +242,21 @@ func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_mo return false, err } if rule == nil { - return false, nil + // if no rule is found, then user with write access can make official reviews + err := pr.LoadBaseRepo(ctx) + if err != nil { + return false, err + } + writeAccess, err := access_model.HasAccessUnit(ctx, reviewer, pr.BaseRepo, unit.TypeCode, perm.AccessModeWrite) + if err != nil { + return false, err + } + return writeAccess, nil } - for _, reviewer := range reviewers { - official, err := git_model.IsUserOfficialReviewer(ctx, rule, reviewer) - if official || err != nil { - return official, err - } + official, err := git_model.IsUserOfficialReviewer(ctx, rule, reviewer) + if official || err != nil { + return official, err } return false, nil @@ -322,8 +329,8 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss } // ReviewExists returns whether a review exists for a particular line of code in the PR -func ReviewExists(issue *Issue, treePath string, line int64) (bool, error) { - return db.GetEngine(db.DefaultContext).Cols("id").Exist(&Comment{IssueID: issue.ID, TreePath: treePath, Line: line, Type: CommentTypeCode}) +func ReviewExists(ctx context.Context, issue *Issue, treePath string, line int64) (bool, error) { + return db.GetEngine(ctx).Cols("id").Exist(&Comment{IssueID: issue.ID, TreePath: treePath, Line: line, Type: CommentTypeCode}) } // ContentEmptyErr represents an content empty error @@ -340,8 +347,8 @@ func IsContentEmptyErr(err error) bool { } // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist -func SubmitReview(doer *user_model.User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool, attachmentUUIDs []string) (*Review, *Comment, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool, attachmentUUIDs []string) (*Review, *Comment, error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, nil, err } @@ -487,15 +494,15 @@ func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int6 } // MarkReviewsAsStale marks existing reviews as stale -func MarkReviewsAsStale(issueID int64) (err error) { - _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID) +func MarkReviewsAsStale(ctx context.Context, issueID int64) (err error) { + _, err = db.GetEngine(ctx).Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID) return err } // MarkReviewsAsNotStale marks existing reviews as not stale for a giving commit SHA -func MarkReviewsAsNotStale(issueID int64, commitID string) (err error) { - _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID) +func MarkReviewsAsNotStale(ctx context.Context, issueID int64, commitID string) (err error) { + _, err = db.GetEngine(ctx).Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID) return err } @@ -518,8 +525,8 @@ func DismissReview(ctx context.Context, review *Review, isDismiss bool) (err err } // InsertReviews inserts review and review comments -func InsertReviews(reviews []*Review) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func InsertReviews(ctx context.Context, reviews []*Review) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -578,7 +585,9 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo return nil, nil } - official, err := IsOfficialReviewer(ctx, issue, reviewer, doer) + // if the reviewer is an official reviewer, + // remove the official flag in the all previous reviews + official, err := IsOfficialReviewer(ctx, issue, reviewer) if err != nil { return nil, err } else if official { @@ -794,7 +803,7 @@ func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organi } // MarkConversation Add or remove Conversation mark for a code comment -func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) (err error) { +func MarkConversation(ctx context.Context, comment *Comment, doer *user_model.User, isResolve bool) (err error) { if comment.Type != CommentTypeCode { return nil } @@ -804,7 +813,7 @@ func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) ( return nil } - if _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", doer.ID, comment.ID); err != nil { + if _, err = db.GetEngine(ctx).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", doer.ID, comment.ID); err != nil { return err } } else { @@ -812,7 +821,7 @@ func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) ( return nil } - if _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", 0, comment.ID); err != nil { + if _, err = db.GetEngine(ctx).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", 0, comment.ID); err != nil { return err } } @@ -822,24 +831,24 @@ func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) ( // CanMarkConversation Add or remove Conversation mark for a code comment permission check // the PR writer , offfcial reviewer and poster can do it -func CanMarkConversation(issue *Issue, doer *user_model.User) (permResult bool, err error) { +func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.User) (permResult bool, err error) { if doer == nil || issue == nil { return false, fmt.Errorf("issue or doer is nil") } if doer.ID != issue.PosterID { - if err = issue.LoadRepo(db.DefaultContext); err != nil { + if err = issue.LoadRepo(ctx); err != nil { return false, err } - p, err := access_model.GetUserRepoPermission(db.DefaultContext, issue.Repo, doer) + p, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) if err != nil { return false, err } permResult = p.CanAccess(perm.AccessModeWrite, unit.TypePullRequests) if !permResult { - if permResult, err = IsOfficialReviewer(db.DefaultContext, issue, doer); err != nil { + if permResult, err = IsOfficialReviewer(ctx, issue, doer); err != nil { return false, err } } @@ -853,8 +862,8 @@ func CanMarkConversation(issue *Issue, doer *user_model.User) (permResult bool, } // DeleteReview delete a review and it's code comments -func DeleteReview(r *Review) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteReview(ctx context.Context, r *Review) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -903,7 +912,7 @@ func DeleteReview(r *Review) error { } // GetCodeCommentsCount return count of CodeComments a Review has -func (r *Review) GetCodeCommentsCount() int { +func (r *Review) GetCodeCommentsCount(ctx context.Context) int { opts := FindCommentsOptions{ Type: CommentTypeCode, IssueID: r.IssueID, @@ -914,7 +923,7 @@ func (r *Review) GetCodeCommentsCount() int { conds = conds.And(builder.Eq{"invalidated": false}) } - count, err := db.GetEngine(db.DefaultContext).Where(conds).Count(new(Comment)) + count, err := db.GetEngine(ctx).Where(conds).Count(new(Comment)) if err != nil { return 0 } @@ -922,18 +931,18 @@ func (r *Review) GetCodeCommentsCount() int { } // HTMLURL formats a URL-string to the related review issue-comment -func (r *Review) HTMLURL() string { +func (r *Review) HTMLURL(ctx context.Context) string { opts := FindCommentsOptions{ Type: CommentTypeReview, IssueID: r.IssueID, ReviewID: r.ID, } comment := new(Comment) - has, err := db.GetEngine(db.DefaultContext).Where(opts.ToConds()).Get(comment) + has, err := db.GetEngine(ctx).Where(opts.ToConds()).Get(comment) if err != nil || !has { return "" } - return comment.HTMLURL() + return comment.HTMLURL(ctx) } // RemapExternalUser ExternalUserRemappable interface @@ -954,8 +963,8 @@ func (r *Review) GetExternalName() string { return r.OriginalAuthor } func (r *Review) GetExternalID() int64 { return r.OriginalAuthorID } // UpdateReviewsMigrationsByType updates reviews' migrations information via given git service type and original id and poster id -func UpdateReviewsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("review"). +func UpdateReviewsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("review"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(tp)). Update(map[string]any{ diff --git a/models/issues/review_list.go b/models/issues/review_list.go index 9f50d8e09d..ed3d0bd028 100644 --- a/models/issues/review_list.go +++ b/models/issues/review_list.go @@ -126,16 +126,16 @@ func FindLatestReviews(ctx context.Context, opts FindReviewOptions) (ReviewList, } // CountReviews returns count of reviews passing FindReviewOptions -func CountReviews(opts FindReviewOptions) (int64, error) { - return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Review{}) +func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{}) } // GetReviewersFromOriginalAuthorsByIssueID gets the latest review of each original authors for a pull request -func GetReviewersFromOriginalAuthorsByIssueID(issueID int64) (ReviewList, error) { +func GetReviewersFromOriginalAuthorsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) { reviews := make([]*Review, 0, 10) // Get latest review of each reviewer, sorted in order they were made - if err := db.GetEngine(db.DefaultContext).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC", + if err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC", issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). Find(&reviews); err != nil { return nil, err @@ -145,10 +145,10 @@ func GetReviewersFromOriginalAuthorsByIssueID(issueID int64) (ReviewList, error) } // GetReviewsByIssueID gets the latest review of each reviewer for a pull request -func GetReviewsByIssueID(issueID int64) (ReviewList, error) { +func GetReviewsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) { reviews := make([]*Review, 0, 10) - sess := db.GetEngine(db.DefaultContext) + sess := db.GetEngine(ctx) // Get latest review of each reviewer, sorted in order they were made if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND dismissed = ? AND original_author_id = 0 GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC", diff --git a/models/issues/review_test.go b/models/issues/review_test.go index 19816e864b..63e2aef2cc 100644 --- a/models/issues/review_test.go +++ b/models/issues/review_test.go @@ -80,7 +80,7 @@ func TestFindLatestReviews(t *testing.T) { assert.NoError(t, err) assert.Len(t, reviews, 2) assert.Equal(t, "duplicate review from user5 (latest)", reviews[0].Content) - assert.Equal(t, "singular review from user6 and final review for this pr", reviews[1].Content) + assert.Equal(t, "singular review from org6 and final review for this pr", reviews[1].Content) } func TestGetCurrentReview(t *testing.T) { @@ -123,13 +123,13 @@ func TestGetReviewersByIssueID(t *testing.T) { issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) expectedReviews := []*issues_model.Review{} expectedReviews = append(expectedReviews, &issues_model.Review{ - Reviewer: user3, + Reviewer: org3, Type: issues_model.ReviewTypeReject, UpdatedUnix: 946684812, }, @@ -144,7 +144,7 @@ func TestGetReviewersByIssueID(t *testing.T) { UpdatedUnix: 946684814, }) - allReviews, err := issues_model.GetReviewsByIssueID(issue.ID) + allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID) assert.NoError(t, err) for _, review := range allReviews { assert.NoError(t, review.LoadReviewer(db.DefaultContext)) @@ -157,7 +157,7 @@ func TestGetReviewersByIssueID(t *testing.T) { } } - allReviews, err = issues_model.GetReviewsByIssueID(issue.ID) + allReviews, err = issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID) assert.NoError(t, err) assert.NoError(t, allReviews.LoadReviewers(db.DefaultContext)) if assert.Len(t, allReviews, 3) { @@ -248,7 +248,7 @@ func TestDeleteReview(t *testing.T) { }) assert.NoError(t, err) - assert.NoError(t, issues_model.DeleteReview(review2)) + assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review2)) _, err = issues_model.GetReviewByID(db.DefaultContext, review2.ID) assert.Error(t, err) diff --git a/models/issues/stopwatch.go b/models/issues/stopwatch.go index c8cd5ad33f..2c662bdb06 100644 --- a/models/issues/stopwatch.go +++ b/models/issues/stopwatch.go @@ -81,9 +81,9 @@ type UserStopwatch struct { } // GetUIDsAndNotificationCounts between the two provided times -func GetUIDsAndStopwatch() ([]*UserStopwatch, error) { +func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) { sws := []*Stopwatch{} - if err := db.GetEngine(db.DefaultContext).Where("issue_id != 0").Find(&sws); err != nil { + if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil { return nil, err } if len(sws) == 0 { @@ -107,9 +107,9 @@ func GetUIDsAndStopwatch() ([]*UserStopwatch, error) { } // GetUserStopwatches return list of all stopwatches of a user -func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) { +func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) { sws := make([]*Stopwatch, 0, 8) - sess := db.GetEngine(db.DefaultContext).Where("stopwatch.user_id = ?", userID) + sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID) if listOptions.Page != 0 { sess = db.SetSessionPagination(sess, &listOptions) } @@ -122,13 +122,13 @@ func GetUserStopwatches(userID int64, listOptions db.ListOptions) ([]*Stopwatch, } // CountUserStopwatches return count of all stopwatches of a user -func CountUserStopwatches(userID int64) (int64, error) { - return db.GetEngine(db.DefaultContext).Where("user_id = ?", userID).Count(&Stopwatch{}) +func CountUserStopwatches(ctx context.Context, userID int64) (int64, error) { + return db.GetEngine(ctx).Where("user_id = ?", userID).Count(&Stopwatch{}) } // StopwatchExists returns true if the stopwatch exists -func StopwatchExists(userID, issueID int64) bool { - _, exists, _ := getStopwatch(db.DefaultContext, userID, issueID) +func StopwatchExists(ctx context.Context, userID, issueID int64) bool { + _, exists, _ := getStopwatch(ctx, userID, issueID) return exists } @@ -168,15 +168,15 @@ func FinishIssueStopwatchIfPossible(ctx context.Context, user *user_model.User, } // CreateOrStopIssueStopwatch create an issue stopwatch if it's not exist, otherwise finish it -func CreateOrStopIssueStopwatch(user *user_model.User, issue *Issue) error { - _, exists, err := getStopwatch(db.DefaultContext, user.ID, issue.ID) +func CreateOrStopIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error { + _, exists, err := getStopwatch(ctx, user.ID, issue.ID) if err != nil { return err } if exists { - return FinishIssueStopwatch(db.DefaultContext, user, issue) + return FinishIssueStopwatch(ctx, user, issue) } - return CreateIssueStopwatch(db.DefaultContext, user, issue) + return CreateIssueStopwatch(ctx, user, issue) } // FinishIssueStopwatch if stopwatch exist then finish it otherwise return an error @@ -269,8 +269,8 @@ func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss } // CancelStopwatch removes the given stopwatch and logs it into issue's timeline. -func CancelStopwatch(user *user_model.User, issue *Issue) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/stopwatch_test.go b/models/issues/stopwatch_test.go index ea3827a1f6..39958a7f36 100644 --- a/models/issues/stopwatch_test.go +++ b/models/issues/stopwatch_test.go @@ -26,20 +26,20 @@ func TestCancelStopwatch(t *testing.T) { issue2, err := issues_model.GetIssueByID(db.DefaultContext, 2) assert.NoError(t, err) - err = issues_model.CancelStopwatch(user1, issue1) + err = issues_model.CancelStopwatch(db.DefaultContext, user1, issue1) assert.NoError(t, err) unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: user1.ID, IssueID: issue1.ID}) _ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID}) - assert.Nil(t, issues_model.CancelStopwatch(user1, issue2)) + assert.Nil(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2)) } func TestStopwatchExists(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.True(t, issues_model.StopwatchExists(1, 1)) - assert.False(t, issues_model.StopwatchExists(1, 2)) + assert.True(t, issues_model.StopwatchExists(db.DefaultContext, 1, 1)) + assert.False(t, issues_model.StopwatchExists(db.DefaultContext, 1, 2)) } func TestHasUserStopwatch(t *testing.T) { @@ -60,7 +60,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) { user2, err := user_model.GetUserByID(db.DefaultContext, 2) assert.NoError(t, err) - user3, err := user_model.GetUserByID(db.DefaultContext, 3) + org3, err := user_model.GetUserByID(db.DefaultContext, 3) assert.NoError(t, err) issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1) @@ -68,11 +68,11 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) { issue2, err := issues_model.GetIssueByID(db.DefaultContext, 2) assert.NoError(t, err) - assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(user3, issue1)) + assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(db.DefaultContext, org3, issue1)) sw := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: 3, IssueID: 1}) assert.LessOrEqual(t, sw.CreatedUnix, timeutil.TimeStampNow()) - assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(user2, issue2)) + assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(db.DefaultContext, user2, issue2)) unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: 2, IssueID: 2}) unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 2, IssueID: 2}) } diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go index 58c6b775f0..795bddeb34 100644 --- a/models/issues/tracked_time.go +++ b/models/issues/tracked_time.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/util" "xorm.io/builder" + "xorm.io/xorm" ) // TrackedTime represents a time that was spent for a specific issue. @@ -198,9 +199,9 @@ func addTime(ctx context.Context, user *user_model.User, issue *Issue, amount in return tt, db.Insert(ctx, tt) } -// TotalTimes returns the spent time in seconds for each user by an issue -func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]int64, error) { - trackedTimes, err := GetTrackedTimes(db.DefaultContext, options) +// TotalTimesForEachUser returns the spent time in seconds for each user by an issue +func TotalTimesForEachUser(ctx context.Context, options *FindTrackedTimesOptions) (map[*user_model.User]int64, error) { + trackedTimes, err := GetTrackedTimes(ctx, options) if err != nil { return nil, err } @@ -213,7 +214,7 @@ func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]int64, e totalTimes := make(map[*user_model.User]int64) // Fetching User and making time human readable for userID, total := range totalTimesByUser { - user, err := user_model.GetUserByID(db.DefaultContext, userID) + user, err := user_model.GetUserByID(ctx, userID) if err != nil { if user_model.IsErrUserNotExist(err) { continue @@ -226,8 +227,8 @@ func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]int64, e } // DeleteIssueUserTimes deletes times for issue -func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteIssueUserTimes(ctx context.Context, issue *Issue, user *user_model.User) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -265,8 +266,8 @@ func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error { } // DeleteTime delete a specific Time -func DeleteTime(t *TrackedTime) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteTime(ctx context.Context, t *TrackedTime) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -315,9 +316,9 @@ func deleteTime(ctx context.Context, t *TrackedTime) error { } // GetTrackedTimeByID returns raw TrackedTime without loading attributes by id -func GetTrackedTimeByID(id int64) (*TrackedTime, error) { +func GetTrackedTimeByID(ctx context.Context, id int64) (*TrackedTime, error) { time := new(TrackedTime) - has, err := db.GetEngine(db.DefaultContext).ID(id).Get(time) + has, err := db.GetEngine(ctx).ID(id).Get(time) if err != nil { return nil, err } else if !has { @@ -325,3 +326,46 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) { } return time, nil } + +// GetIssueTotalTrackedTime returns the total tracked time for issues by given conditions. +func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed bool) (int64, error) { + if len(opts.IssueIDs) <= MaxQueryParameters { + return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs) + } + + // If too long a list of IDs is provided, + // we get the statistics in smaller chunks and get accumulates + var accum int64 + for i := 0; i < len(opts.IssueIDs); { + chunk := i + MaxQueryParameters + if chunk > len(opts.IssueIDs) { + chunk = len(opts.IssueIDs) + } + time, err := getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs[i:chunk]) + if err != nil { + return 0, err + } + accum += time + i = chunk + } + return accum, nil +} + +func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed bool, issueIDs []int64) (int64, error) { + sumSession := func(opts *IssuesOptions, issueIDs []int64) *xorm.Session { + sess := db.GetEngine(ctx). + Table("tracked_time"). + Where("tracked_time.deleted = ?", false). + Join("INNER", "issue", "tracked_time.issue_id = issue.id") + + return applyIssuesOptions(sess, opts, issueIDs) + } + + type trackedTime struct { + Time int64 + } + + return sumSession(opts, issueIDs). + And("issue.is_closed = ?", isClosed). + SumInt(new(trackedTime), "tracked_time.time") +} diff --git a/models/issues/tracked_time_test.go b/models/issues/tracked_time_test.go index 1d88109183..2774234e7b 100644 --- a/models/issues/tracked_time_test.go +++ b/models/issues/tracked_time_test.go @@ -18,14 +18,14 @@ import ( func TestAddTime(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - user3, err := user_model.GetUserByID(db.DefaultContext, 3) + org3, err := user_model.GetUserByID(db.DefaultContext, 3) assert.NoError(t, err) issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1) assert.NoError(t, err) // 3661 = 1h 1min 1s - trackedTime, err := issues_model.AddTime(db.DefaultContext, user3, issue1, 3661, time.Now()) + trackedTime, err := issues_model.AddTime(db.DefaultContext, org3, issue1, 3661, time.Now()) assert.NoError(t, err) assert.Equal(t, int64(3), trackedTime.UserID) assert.Equal(t, int64(1), trackedTime.IssueID) @@ -79,10 +79,10 @@ func TestGetTrackedTimes(t *testing.T) { assert.Len(t, times, 0) } -func TestTotalTimes(t *testing.T) { +func TestTotalTimesForEachUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - total, err := issues_model.TotalTimes(&issues_model.FindTrackedTimesOptions{IssueID: 1}) + total, err := issues_model.TotalTimesForEachUser(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: 1}) assert.NoError(t, err) assert.Len(t, total, 1) for user, time := range total { @@ -90,7 +90,7 @@ func TestTotalTimes(t *testing.T) { assert.EqualValues(t, 400, time) } - total, err = issues_model.TotalTimes(&issues_model.FindTrackedTimesOptions{IssueID: 2}) + total, err = issues_model.TotalTimesForEachUser(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: 2}) assert.NoError(t, err) assert.Len(t, total, 2) for user, time := range total { @@ -103,7 +103,7 @@ func TestTotalTimes(t *testing.T) { } } - total, err = issues_model.TotalTimes(&issues_model.FindTrackedTimesOptions{IssueID: 5}) + total, err = issues_model.TotalTimesForEachUser(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: 5}) assert.NoError(t, err) assert.Len(t, total, 1) for user, time := range total { @@ -111,7 +111,19 @@ func TestTotalTimes(t *testing.T) { assert.EqualValues(t, 1, time) } - total, err = issues_model.TotalTimes(&issues_model.FindTrackedTimesOptions{IssueID: 4}) + total, err = issues_model.TotalTimesForEachUser(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: 4}) assert.NoError(t, err) assert.Len(t, total, 2) } + +func TestGetIssueTotalTrackedTime(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, false) + assert.NoError(t, err) + assert.EqualValues(t, 3682, ttt) + + ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, true) + assert.NoError(t, err) + assert.EqualValues(t, 0, ttt) +} diff --git a/models/main_test.go b/models/main_test.go index c09b661d2c..600dcc889b 100644 --- a/models/main_test.go +++ b/models/main_test.go @@ -29,7 +29,5 @@ func TestFixturesAreConsistent(t *testing.T) { } func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: "..", - }) + unittest.MainTest(m) } diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml index 96104ac04a..b02cb570ec 100644 --- a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml +++ b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml @@ -26,4 +26,3 @@ id: 5 issue_id: 2 label_id: 87 - diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml index d651c87d5b..fa9658aa02 100644 --- a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml +++ b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml @@ -18,7 +18,7 @@ - id: 3 repo_id: 0 - org_id: 3 + org_id: 3 name: orglabel3 color: '#abcdef' num_issues: 0 diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml b/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml index 094b811d91..0f5a3eb507 100644 --- a/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml +++ b/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml @@ -23,4 +23,3 @@ id: 5 repo_id: 3 org_id: 0 - diff --git a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml index c02a76e374..ebb73f44de 100644 --- a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml +++ b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml @@ -28,4 +28,3 @@ attestation_type: 'fido-u2f' sign_count: 1 clone_warning: false - diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index f0a8b05d53..4a06cdc73a 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/models/migrations/v1_19" "code.gitea.io/gitea/models/migrations/v1_20" "code.gitea.io/gitea/models/migrations/v1_21" + "code.gitea.io/gitea/models/migrations/v1_22" "code.gitea.io/gitea/models/migrations/v1_6" "code.gitea.io/gitea/models/migrations/v1_7" "code.gitea.io/gitea/models/migrations/v1_8" @@ -532,6 +533,21 @@ var migrations = []Migration{ NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable), // v275 -> v276 NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun), + // v276 -> v277 + NewMigration("Add RemoteAddress to mirrors", v1_21.AddRemoteAddressToMirrors), + // v277 -> v278 + NewMigration("Add Index to issue_user.issue_id", v1_21.AddIndexToIssueUserIssueID), + // v278 -> v279 + NewMigration("Add Index to comment.dependent_issue_id", v1_21.AddIndexToCommentDependentIssueID), + // v279 -> v280 + NewMigration("Add Index to action.user_id", v1_21.AddIndexToActionUserID), + + // Gitea 1.21.0 ends at 280 + + // v280 -> v281 + NewMigration("Rename user themes", v1_22.RenameUserThemes), + // v281 -> v282 + NewMigration("Add auth_token table", v1_22.CreateAuthTokenTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_18/v227.go b/models/migrations/v1_18/v227.go index 78c80f74ec..5fe5dcd0c9 100644 --- a/models/migrations/v1_18/v227.go +++ b/models/migrations/v1_18/v227.go @@ -4,10 +4,6 @@ package v1_18 //nolint import ( - "fmt" - "strconv" - - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "xorm.io/xorm" @@ -22,42 +18,6 @@ type SystemSetting struct { Updated timeutil.TimeStamp `xorm:"updated"` } -func insertSettingsIfNotExist(x *xorm.Engine, sysSettings []*SystemSetting) error { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - for _, setting := range sysSettings { - exist, err := sess.Table("system_setting").Where("setting_key=?", setting.SettingKey).Exist() - if err != nil { - return err - } - if !exist { - if _, err := sess.Insert(setting); err != nil { - return err - } - } - } - return sess.Commit() -} - func CreateSystemSettingsTable(x *xorm.Engine) error { - if err := x.Sync(new(SystemSetting)); err != nil { - return fmt.Errorf("sync2: %w", err) - } - - // migrate xx to database - sysSettings := []*SystemSetting{ - { - SettingKey: "picture.disable_gravatar", - SettingValue: strconv.FormatBool(setting.DisableGravatar), - }, - { - SettingKey: "picture.enable_federated_avatar", - SettingValue: strconv.FormatBool(setting.EnableFederatedAvatar), - }, - } - - return insertSettingsIfNotExist(x, sysSettings) + return x.Sync(new(SystemSetting)) } diff --git a/models/migrations/v1_21/v266.go b/models/migrations/v1_21/v266.go index df85286c89..79a5f5e14c 100644 --- a/models/migrations/v1_21/v266.go +++ b/models/migrations/v1_21/v266.go @@ -18,9 +18,6 @@ func ReduceCommitStatus(x *xorm.Engine) error { if _, err := sess.Exec(`UPDATE commit_status SET state='pending' WHERE state='running'`); err != nil { return err } - if _, err := sess.Exec(`UPDATE commit_status SET state='failure' WHERE state='warning'`); err != nil { - return err - } return sess.Commit() } diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go new file mode 100644 index 0000000000..ed1bc3bda5 --- /dev/null +++ b/models/migrations/v1_21/v276.go @@ -0,0 +1,179 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "context" + "fmt" + "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/git" + giturl "code.gitea.io/gitea/modules/git/url" + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +func AddRemoteAddressToMirrors(x *xorm.Engine) error { + type Mirror struct { + RemoteAddress string `xorm:"VARCHAR(2048)"` + } + + type PushMirror struct { + RemoteAddress string `xorm:"VARCHAR(2048)"` + } + + if err := x.Sync(new(Mirror), new(PushMirror)); err != nil { + return err + } + + if err := migratePullMirrors(x); err != nil { + return err + } + + return migratePushMirrors(x) +} + +func migratePullMirrors(x *xorm.Engine) error { + type Mirror struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + RemoteAddress string `xorm:"VARCHAR(2048)"` + RepoOwner string + RepoName string + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + limit := setting.Database.IterateBufferSize + if limit <= 0 { + limit = 50 + } + + start := 0 + + for { + var mirrors []Mirror + if err := sess.Select("mirror.id, mirror.repo_id, mirror.remote_address, repository.owner_name as repo_owner, repository.name as repo_name"). + Join("INNER", "repository", "repository.id = mirror.repo_id"). + Limit(limit, start).Find(&mirrors); err != nil { + return err + } + + if len(mirrors) == 0 { + break + } + start += len(mirrors) + + for _, m := range mirrors { + remoteAddress, err := getRemoteAddress(m.RepoOwner, m.RepoName, "origin") + if err != nil { + return err + } + + m.RemoteAddress = remoteAddress + + if _, err = sess.ID(m.ID).Cols("remote_address").Update(m); err != nil { + return err + } + } + + if start%1000 == 0 { // avoid a too big transaction + if err := sess.Commit(); err != nil { + return err + } + if err := sess.Begin(); err != nil { + return err + } + } + } + + return sess.Commit() +} + +func migratePushMirrors(x *xorm.Engine) error { + type PushMirror struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + RemoteName string + RemoteAddress string `xorm:"VARCHAR(2048)"` + RepoOwner string + RepoName string + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + limit := setting.Database.IterateBufferSize + if limit <= 0 { + limit = 50 + } + + start := 0 + + for { + var mirrors []PushMirror + if err := sess.Select("push_mirror.id, push_mirror.repo_id, push_mirror.remote_name, push_mirror.remote_address, repository.owner_name as repo_owner, repository.name as repo_name"). + Join("INNER", "repository", "repository.id = push_mirror.repo_id"). + Limit(limit, start).Find(&mirrors); err != nil { + return err + } + + if len(mirrors) == 0 { + break + } + start += len(mirrors) + + for _, m := range mirrors { + remoteAddress, err := getRemoteAddress(m.RepoOwner, m.RepoName, m.RemoteName) + if err != nil { + return err + } + + m.RemoteAddress = remoteAddress + + if _, err = sess.ID(m.ID).Cols("remote_address").Update(m); err != nil { + return err + } + } + + if start%1000 == 0 { // avoid a too big transaction + if err := sess.Commit(); err != nil { + return err + } + if err := sess.Begin(); err != nil { + return err + } + } + } + + return sess.Commit() +} + +func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) { + repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git") + + remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName) + if err != nil { + return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err) + } + + u, err := giturl.Parse(remoteURL) + if err != nil { + return "", err + } + u.User = nil + + return u.String(), nil +} diff --git a/models/migrations/v1_21/v277.go b/models/migrations/v1_21/v277.go new file mode 100644 index 0000000000..12529160b7 --- /dev/null +++ b/models/migrations/v1_21/v277.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddIndexToIssueUserIssueID(x *xorm.Engine) error { + type IssueUser struct { + IssueID int64 `xorm:"INDEX"` + } + + return x.Sync(new(IssueUser)) +} diff --git a/models/migrations/v1_21/v278.go b/models/migrations/v1_21/v278.go new file mode 100644 index 0000000000..d6a462d1e7 --- /dev/null +++ b/models/migrations/v1_21/v278.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddIndexToCommentDependentIssueID(x *xorm.Engine) error { + type Comment struct { + DependentIssueID int64 `xorm:"index"` + } + + return x.Sync(new(Comment)) +} diff --git a/models/migrations/v1_21/v279.go b/models/migrations/v1_21/v279.go new file mode 100644 index 0000000000..19647225c9 --- /dev/null +++ b/models/migrations/v1_21/v279.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddIndexToActionUserID(x *xorm.Engine) error { + type Action struct { + UserID int64 `xorm:"INDEX"` + } + + return x.Sync(new(Action)) +} diff --git a/models/migrations/v1_22/v280.go b/models/migrations/v1_22/v280.go new file mode 100644 index 0000000000..a8ee4a3bf7 --- /dev/null +++ b/models/migrations/v1_22/v280.go @@ -0,0 +1,29 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "xorm.io/xorm" +) + +func RenameUserThemes(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + if _, err := sess.Exec("UPDATE `user` SET `theme` = 'gitea-light' WHERE `theme` = 'gitea'"); err != nil { + return err + } + if _, err := sess.Exec("UPDATE `user` SET `theme` = 'gitea-dark' WHERE `theme` = 'arc-green'"); err != nil { + return err + } + if _, err := sess.Exec("UPDATE `user` SET `theme` = 'gitea-auto' WHERE `theme` = 'auto'"); err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/v1_22/v281.go b/models/migrations/v1_22/v281.go new file mode 100644 index 0000000000..fc1866aa83 --- /dev/null +++ b/models/migrations/v1_22/v281.go @@ -0,0 +1,21 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func CreateAuthTokenTable(x *xorm.Engine) error { + type AuthToken struct { + ID string `xorm:"pk"` + TokenHash string + UserID int64 `xorm:"INDEX"` + ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"` + } + + return x.Sync(new(AuthToken)) +} diff --git a/models/org.go b/models/org.go index 5f0e678ab4..119465b962 100644 --- a/models/org.go +++ b/models/org.go @@ -97,8 +97,8 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { } // RemoveOrgUser removes user from given organization. -func RemoveOrgUser(orgID, userID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func RemoveOrgUser(ctx context.Context, orgID, userID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/org_team.go b/models/org_team.go index cf3680990d..acbab56089 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -73,8 +73,8 @@ func addAllRepositories(ctx context.Context, t *organization.Team) error { } // AddAllRepositories adds all repositories to the team -func AddAllRepositories(t *organization.Team) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func AddAllRepositories(ctx context.Context, t *organization.Team) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -88,12 +88,12 @@ func AddAllRepositories(t *organization.Team) (err error) { } // RemoveAllRepositories removes all repositories from team and recalculates access -func RemoveAllRepositories(t *organization.Team) (err error) { +func RemoveAllRepositories(ctx context.Context, t *organization.Team) (err error) { if t.IncludesAllRepositories { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -153,7 +153,7 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error // NewTeam creates a record of new team. // It's caller's responsibility to assign organization ID. -func NewTeam(t *organization.Team) (err error) { +func NewTeam(ctx context.Context, t *organization.Team) (err error) { if len(t.Name) == 0 { return util.NewInvalidArgumentErrorf("empty team name") } @@ -162,7 +162,7 @@ func NewTeam(t *organization.Team) (err error) { return err } - has, err := db.GetEngine(db.DefaultContext).ID(t.OrgID).Get(new(user_model.User)) + has, err := db.GetEngine(ctx).ID(t.OrgID).Get(new(user_model.User)) if err != nil { return err } @@ -171,7 +171,7 @@ func NewTeam(t *organization.Team) (err error) { } t.LowerName = strings.ToLower(t.Name) - has, err = db.GetEngine(db.DefaultContext). + has, err = db.GetEngine(ctx). Where("org_id=?", t.OrgID). And("lower_name=?", t.LowerName). Get(new(organization.Team)) @@ -182,7 +182,7 @@ func NewTeam(t *organization.Team) (err error) { return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName} } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -218,7 +218,7 @@ func NewTeam(t *organization.Team) (err error) { } // UpdateTeam updates information of team. -func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err error) { +func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeAllChanged bool) (err error) { if len(t.Name) == 0 { return util.NewInvalidArgumentErrorf("empty team name") } @@ -227,7 +227,7 @@ func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err t.Description = t.Description[:255] } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -293,8 +293,8 @@ func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err // DeleteTeam deletes given team. // It's caller's responsibility to assign organization ID. -func DeleteTeam(t *organization.Team) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteTeam(ctx context.Context, t *organization.Team) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -356,85 +356,81 @@ func DeleteTeam(t *organization.Team) error { // AddTeamMember adds new membership of given team to given organization, // the user will have membership to given organization automatically when needed. -func AddTeamMember(team *organization.Team, userID int64) error { - isAlreadyMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, userID) +func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) error { + isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID) if err != nil || isAlreadyMember { return err } - if err := organization.AddOrgUser(team.OrgID, userID); err != nil { + if err := organization.AddOrgUser(ctx, team.OrgID, userID); err != nil { return err } - ctx, committer, err := db.TxContext(db.DefaultContext) + err = db.WithTx(ctx, func(ctx context.Context) error { + // check in transaction + isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, userID) + if err != nil || isAlreadyMember { + return err + } + + sess := db.GetEngine(ctx) + + if err := db.Insert(ctx, &organization.TeamUser{ + UID: userID, + OrgID: team.OrgID, + TeamID: team.ID, + }); err != nil { + return err + } else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team)); err != nil { + return err + } + + team.NumMembers++ + + // Give access to team repositories. + // update exist access if mode become bigger + subQuery := builder.Select("repo_id").From("team_repo"). + Where(builder.Eq{"team_id": team.ID}) + + if _, err := sess.Where("user_id=?", userID). + In("repo_id", subQuery). + And("mode < ?", team.AccessMode). + SetExpr("mode", team.AccessMode). + Update(new(access_model.Access)); err != nil { + return fmt.Errorf("update user accesses: %w", err) + } + + // for not exist access + var repoIDs []int64 + accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID}) + if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil { + return fmt.Errorf("select id accesses: %w", err) + } + + accesses := make([]*access_model.Access, 0, 100) + for i, repoID := range repoIDs { + accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode}) + if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 { + if err = db.Insert(ctx, accesses); err != nil { + return fmt.Errorf("insert new user accesses: %w", err) + } + accesses = accesses[:0] + } + } + return nil + }) if err != nil { return err } - defer committer.Close() - - // check in transaction - isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, userID) - if err != nil || isAlreadyMember { - return err - } - - sess := db.GetEngine(ctx) - - if err := db.Insert(ctx, &organization.TeamUser{ - UID: userID, - OrgID: team.OrgID, - TeamID: team.ID, - }); err != nil { - return err - } else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team)); err != nil { - return err - } - - team.NumMembers++ - - // Give access to team repositories. - // update exist access if mode become bigger - subQuery := builder.Select("repo_id").From("team_repo"). - Where(builder.Eq{"team_id": team.ID}) - - if _, err := sess.Where("user_id=?", userID). - In("repo_id", subQuery). - And("mode < ?", team.AccessMode). - SetExpr("mode", team.AccessMode). - Update(new(access_model.Access)); err != nil { - return fmt.Errorf("update user accesses: %w", err) - } - - // for not exist access - var repoIDs []int64 - accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID}) - if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil { - return fmt.Errorf("select id accesses: %w", err) - } - - accesses := make([]*access_model.Access, 0, 100) - for i, repoID := range repoIDs { - accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode}) - if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 { - if err = db.Insert(ctx, accesses); err != nil { - return fmt.Errorf("insert new user accesses: %w", err) - } - accesses = accesses[:0] - } - } - - if err := committer.Commit(); err != nil { - return err - } - committer.Close() // this behaviour may spend much time so run it in a goroutine // FIXME: Update watch repos batchly if setting.Service.AutoWatchNewRepos { // Get team and its repositories. - if err := team.LoadRepositories(db.DefaultContext); err != nil { - log.Error("getRepositories failed: %v", err) + if err := team.LoadRepositories(ctx); err != nil { + log.Error("team.LoadRepositories failed: %v", err) } + // FIXME: in the goroutine, it can't access the "ctx", it could only use db.DefaultContext at the moment go func(repos []*repo_model.Repository) { for _, repo := range repos { if err = repo_model.WatchRepo(db.DefaultContext, userID, repo.ID, true); err != nil { @@ -512,8 +508,8 @@ func removeInvalidOrgUser(ctx context.Context, userID, orgID int64) error { } // RemoveTeamMember removes member from given team of given organization. -func RemoveTeamMember(team *organization.Team, userID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func RemoveTeamMember(ctx context.Context, team *organization.Team, userID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/org_team_test.go b/models/org_team_test.go index 4978f8ef99..e4b7b917e8 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -23,7 +23,7 @@ func TestTeam_AddMember(t *testing.T) { test := func(teamID, userID int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, AddTeamMember(team, userID)) + assert.NoError(t, AddTeamMember(db.DefaultContext, team, userID)) unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID}) } @@ -37,7 +37,7 @@ func TestTeam_RemoveMember(t *testing.T) { testSuccess := func(teamID, userID int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, RemoveTeamMember(team, userID)) + assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, userID)) unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}) } @@ -47,7 +47,7 @@ func TestTeam_RemoveMember(t *testing.T) { testSuccess(3, unittest.NonexistentID) team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}) - err := RemoveTeamMember(team, 2) + err := RemoveTeamMember(db.DefaultContext, team, 2) assert.True(t, organization.IsErrLastOrgOwner(err)) } @@ -61,7 +61,7 @@ func TestNewTeam(t *testing.T) { const teamName = "newTeamName" team := &organization.Team{Name: teamName, OrgID: 3} - assert.NoError(t, NewTeam(team)) + assert.NoError(t, NewTeam(db.DefaultContext, team)) unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: teamName}) unittest.CheckConsistencyFor(t, &organization.Team{}, &user_model.User{ID: team.OrgID}) } @@ -75,7 +75,7 @@ func TestUpdateTeam(t *testing.T) { team.Name = "newName" team.Description = strings.Repeat("A long description!", 100) team.AccessMode = perm.AccessModeAdmin - assert.NoError(t, UpdateTeam(team, true, false)) + assert.NoError(t, UpdateTeam(db.DefaultContext, team, true, false)) team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"}) assert.True(t, strings.HasPrefix(team.Description, "A long description!")) @@ -94,7 +94,7 @@ func TestUpdateTeam2(t *testing.T) { team.LowerName = "owners" team.Name = "Owners" team.Description = strings.Repeat("A long description!", 100) - err := UpdateTeam(team, true, false) + err := UpdateTeam(db.DefaultContext, team, true, false) assert.True(t, organization.IsErrTeamAlreadyExist(err)) unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) @@ -104,7 +104,7 @@ func TestDeleteTeam(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - assert.NoError(t, DeleteTeam(team)) + assert.NoError(t, DeleteTeam(db.DefaultContext, team)) unittest.AssertNotExistsBean(t, &organization.Team{ID: team.ID}) unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID}) unittest.AssertNotExistsBean(t, &organization.TeamUser{TeamID: team.ID}) @@ -122,7 +122,7 @@ func TestAddTeamMember(t *testing.T) { test := func(teamID, userID int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, AddTeamMember(team, userID)) + assert.NoError(t, AddTeamMember(db.DefaultContext, team, userID)) unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID}) } @@ -136,7 +136,7 @@ func TestRemoveTeamMember(t *testing.T) { testSuccess := func(teamID, userID int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, RemoveTeamMember(team, userID)) + assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, userID)) unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}) } @@ -146,7 +146,7 @@ func TestRemoveTeamMember(t *testing.T) { testSuccess(3, unittest.NonexistentID) team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}) - err := RemoveTeamMember(team, 2) + err := RemoveTeamMember(db.DefaultContext, team, 2) assert.True(t, organization.IsErrLastOrgOwner(err)) } @@ -161,7 +161,7 @@ func TestRepository_RecalculateAccesses3(t *testing.T) { // adding user29 to team5 should add an explicit access row for repo 23 // even though repo 23 is public - assert.NoError(t, AddTeamMember(team5, user29.ID)) + assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29.ID)) has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23}) assert.NoError(t, err) diff --git a/models/org_test.go b/models/org_test.go index 54e8f08465..d10a1dc218 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -6,6 +6,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -20,7 +21,7 @@ func TestUser_RemoveMember(t *testing.T) { // remove a user that is a member unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{UID: 4, OrgID: 3}) prevNumMembers := org.NumMembers - assert.NoError(t, RemoveOrgUser(org.ID, 4)) + assert.NoError(t, RemoveOrgUser(db.DefaultContext, org.ID, 4)) unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 4, OrgID: 3}) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) assert.Equal(t, prevNumMembers-1, org.NumMembers) @@ -28,7 +29,7 @@ func TestUser_RemoveMember(t *testing.T) { // remove a user that is not a member unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3}) prevNumMembers = org.NumMembers - assert.NoError(t, RemoveOrgUser(org.ID, 5)) + assert.NoError(t, RemoveOrgUser(db.DefaultContext, org.ID, 5)) unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3}) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) assert.Equal(t, prevNumMembers, org.NumMembers) @@ -44,7 +45,7 @@ func TestRemoveOrgUser(t *testing.T) { if unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) { expectedNumMembers-- } - assert.NoError(t, RemoveOrgUser(orgID, userID)) + assert.NoError(t, RemoveOrgUser(db.DefaultContext, orgID, userID)) unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: orgID, UID: userID}) org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}) assert.EqualValues(t, expectedNumMembers, org.NumMembers) @@ -52,7 +53,7 @@ func TestRemoveOrgUser(t *testing.T) { testSuccess(3, 4) testSuccess(3, 4) - err := RemoveOrgUser(7, 5) + err := RemoveOrgUser(db.DefaultContext, 7, 5) assert.Error(t, err) assert.True(t, organization.IsErrLastOrgOwner(err)) unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: 7, UID: 5}) diff --git a/models/organization/main_test.go b/models/organization/main_test.go index bc5bde2565..c35898a465 100644 --- a/models/organization/main_test.go +++ b/models/organization/main_test.go @@ -4,7 +4,6 @@ package organization_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -18,7 +17,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/organization/mini_org.go b/models/organization/mini_org.go index b1627b5e6c..b1b24624c5 100644 --- a/models/organization/mini_org.go +++ b/models/organization/mini_org.go @@ -4,6 +4,7 @@ package organization import ( + "context" "fmt" "strings" @@ -19,7 +20,7 @@ import ( type MinimalOrg = Organization // GetUserOrgsList returns all organizations the given user has access to -func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { +func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) { schema, err := db.TableInfo(new(user_model.User)) if err != nil { return nil, err @@ -42,7 +43,7 @@ func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { groupByStr := groupByCols.String() groupByStr = groupByStr[0 : len(groupByStr)-1] - sess := db.GetEngine(db.DefaultContext) + sess := db.GetEngine(ctx) sess = sess.Select(groupByStr+", count(distinct repo_id) as org_count"). Table("user"). Join("INNER", "team", "`team`.org_id = `user`.id"). diff --git a/models/organization/org.go b/models/organization/org.go index 8fd4ad076b..07091194eb 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -96,23 +96,23 @@ func (Organization) TableName() string { } // IsOwnedBy returns true if given user is in the owner team. -func (org *Organization) IsOwnedBy(uid int64) (bool, error) { - return IsOrganizationOwner(db.DefaultContext, org.ID, uid) +func (org *Organization) IsOwnedBy(ctx context.Context, uid int64) (bool, error) { + return IsOrganizationOwner(ctx, org.ID, uid) } // IsOrgAdmin returns true if given user is in the owner team or an admin team. -func (org *Organization) IsOrgAdmin(uid int64) (bool, error) { - return IsOrganizationAdmin(db.DefaultContext, org.ID, uid) +func (org *Organization) IsOrgAdmin(ctx context.Context, uid int64) (bool, error) { + return IsOrganizationAdmin(ctx, org.ID, uid) } // IsOrgMember returns true if given user is member of organization. -func (org *Organization) IsOrgMember(uid int64) (bool, error) { - return IsOrganizationMember(db.DefaultContext, org.ID, uid) +func (org *Organization) IsOrgMember(ctx context.Context, uid int64) (bool, error) { + return IsOrganizationMember(ctx, org.ID, uid) } // CanCreateOrgRepo returns true if given user can create repo in organization -func (org *Organization) CanCreateOrgRepo(uid int64) (bool, error) { - return CanCreateOrgRepo(db.DefaultContext, org.ID, uid) +func (org *Organization) CanCreateOrgRepo(ctx context.Context, uid int64) (bool, error) { + return CanCreateOrgRepo(ctx, org.ID, uid) } // GetTeam returns named team of organization. @@ -135,20 +135,20 @@ func FindOrgTeams(ctx context.Context, orgID int64) ([]*Team, error) { } // LoadTeams load teams if not loaded. -func (org *Organization) LoadTeams() ([]*Team, error) { - return FindOrgTeams(db.DefaultContext, org.ID) +func (org *Organization) LoadTeams(ctx context.Context) ([]*Team, error) { + return FindOrgTeams(ctx, org.ID) } // GetMembers returns all members of organization. -func (org *Organization) GetMembers() (user_model.UserList, map[int64]bool, error) { - return FindOrgMembers(&FindOrgMembersOpts{ +func (org *Organization) GetMembers(ctx context.Context) (user_model.UserList, map[int64]bool, error) { + return FindOrgMembers(ctx, &FindOrgMembersOpts{ OrgID: org.ID, }) } // HasMemberWithUserID returns true if user with userID is part of the u organisation. -func (org *Organization) HasMemberWithUserID(userID int64) bool { - return org.hasMemberWithUserID(db.DefaultContext, userID) +func (org *Organization) HasMemberWithUserID(ctx context.Context, userID int64) bool { + return org.hasMemberWithUserID(ctx, userID) } func (org *Organization) hasMemberWithUserID(ctx context.Context, userID int64) bool { @@ -199,8 +199,8 @@ type FindOrgMembersOpts struct { } // CountOrgMembers counts the organization's members -func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) { - sess := db.GetEngine(db.DefaultContext).Where("org_id=?", opts.OrgID) +func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) { + sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID) if opts.PublicOnly { sess.And("is_public = ?", true) } @@ -208,8 +208,8 @@ func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) { } // FindOrgMembers loads organization members according conditions -func FindOrgMembers(opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bool, error) { - ous, err := GetOrgUsersByOrgID(db.DefaultContext, opts) +func FindOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bool, error) { + ous, err := GetOrgUsersByOrgID(ctx, opts) if err != nil { return nil, nil, err } @@ -221,7 +221,7 @@ func FindOrgMembers(opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bo idsIsPublic[ou.UID] = ou.IsPublic } - users, err := user_model.GetUsersByIDs(ids) + users, err := user_model.GetUsersByIDs(ctx, ids) if err != nil { return nil, nil, err } @@ -271,7 +271,7 @@ func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.Us } // CreateOrganization creates record of a new organization. -func CreateOrganization(org *Organization, owner *user_model.User) (err error) { +func CreateOrganization(ctx context.Context, org *Organization, owner *user_model.User) (err error) { if !owner.CanCreateOrganization() { return ErrUserNotAllowedCreateOrg{} } @@ -280,7 +280,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) { return err } - isExist, err := user_model.IsUserExist(db.DefaultContext, 0, org.Name) + isExist, err := user_model.IsUserExist(ctx, 0, org.Name) if err != nil { return err } else if isExist { @@ -300,7 +300,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) { org.NumMembers = 1 org.Type = user_model.UserTypeOrganization - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -412,9 +412,9 @@ func DeleteOrganization(ctx context.Context, org *Organization) error { } // GetOrgUserMaxAuthorizeLevel returns highest authorize level of user in an organization -func (org *Organization) GetOrgUserMaxAuthorizeLevel(uid int64) (perm.AccessMode, error) { +func (org *Organization) GetOrgUserMaxAuthorizeLevel(ctx context.Context, uid int64) (perm.AccessMode, error) { var authorize perm.AccessMode - _, err := db.GetEngine(db.DefaultContext). + _, err := db.GetEngine(ctx). Select("max(team.authorize)"). Table("team"). Join("INNER", "team_user", "team_user.team_id = team.id"). @@ -468,9 +468,9 @@ func (opts FindOrgOptions) toConds() builder.Cond { } // FindOrgs returns a list of organizations according given conditions -func FindOrgs(opts FindOrgOptions) ([]*Organization, error) { +func FindOrgs(ctx context.Context, opts FindOrgOptions) ([]*Organization, error) { orgs := make([]*Organization, 0, 10) - sess := db.GetEngine(db.DefaultContext). + sess := db.GetEngine(ctx). Where(opts.toConds()). Asc("`user`.name") if opts.Page > 0 && opts.PageSize > 0 { @@ -480,8 +480,8 @@ func FindOrgs(opts FindOrgOptions) ([]*Organization, error) { } // CountOrgs returns total count organizations according options -func CountOrgs(opts FindOrgOptions) (int64, error) { - return db.GetEngine(db.DefaultContext). +func CountOrgs(ctx context.Context, opts FindOrgOptions) (int64, error) { + return db.GetEngine(ctx). Where(opts.toConds()). Count(new(Organization)) } @@ -505,13 +505,13 @@ func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) } // HasOrgsVisible tells if the given user can see at least one of the orgs provided -func HasOrgsVisible(orgs []*Organization, user *user_model.User) bool { +func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model.User) bool { if len(orgs) == 0 { return false } for _, org := range orgs { - if HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user) { + if HasOrgOrUserVisible(ctx, org.AsUser(), user) { return true } } @@ -520,10 +520,10 @@ func HasOrgsVisible(orgs []*Organization, user *user_model.User) bool { // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID // are allowed to create repos. -func GetOrgsCanCreateRepoByUserID(userID int64) ([]*Organization, error) { +func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { orgs := make([]*Organization, 0, 10) - return orgs, db.GetEngine(db.DefaultContext).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). + return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). Where(builder.Eq{"`team_user`.uid": userID}). @@ -550,9 +550,9 @@ func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUs } // ChangeOrgUserStatus changes public or private membership status. -func ChangeOrgUserStatus(orgID, uid int64, public bool) error { +func ChangeOrgUserStatus(ctx context.Context, orgID, uid int64, public bool) error { ou := new(OrgUser) - has, err := db.GetEngine(db.DefaultContext). + has, err := db.GetEngine(ctx). Where("uid=?", uid). And("org_id=?", orgID). Get(ou) @@ -563,18 +563,18 @@ func ChangeOrgUserStatus(orgID, uid int64, public bool) error { } ou.IsPublic = public - _, err = db.GetEngine(db.DefaultContext).ID(ou.ID).Cols("is_public").Update(ou) + _, err = db.GetEngine(ctx).ID(ou.ID).Cols("is_public").Update(ou) return err } // AddOrgUser adds new user to given organization. -func AddOrgUser(orgID, uid int64) error { - isAlreadyMember, err := IsOrganizationMember(db.DefaultContext, orgID, uid) +func AddOrgUser(ctx context.Context, orgID, uid int64) error { + isAlreadyMember, err := IsOrganizationMember(ctx, orgID, uid) if err != nil || isAlreadyMember { return err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -669,19 +669,19 @@ func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]in } // TeamsWithAccessToRepo returns all teams that have given access level to the repository. -func (org *Organization) TeamsWithAccessToRepo(repoID int64, mode perm.AccessMode) ([]*Team, error) { - return GetTeamsWithAccessToRepo(db.DefaultContext, org.ID, repoID, mode) +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(userID int64) ([]int64, error) { - return org.getUserTeamIDs(db.DefaultContext, userID) +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(userID int64) ([]*Team, error) { - return org.getUserTeams(db.DefaultContext, userID) +func (org *Organization) GetUserTeams(ctx context.Context, userID int64) ([]*Team, error) { + return org.getUserTeams(ctx, userID) } // AccessibleReposEnvironment operations involving the repositories that are @@ -733,11 +733,11 @@ func AccessibleReposEnv(ctx context.Context, org *Organization, userID int64) (A // AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org` // that are accessible to the specified team. -func (org *Organization) AccessibleTeamReposEnv(team *Team) AccessibleReposEnvironment { +func (org *Organization) AccessibleTeamReposEnv(ctx context.Context, team *Team) AccessibleReposEnvironment { return &accessibleReposEnv{ org: org, team: team, - ctx: db.DefaultContext, + ctx: ctx, orderBy: db.SearchOrderByRecentUpdated, } } diff --git a/models/organization/org_test.go b/models/organization/org_test.go index 226807232c..aa72fc467e 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -31,7 +31,7 @@ func TestUser_IsOwnedBy(t *testing.T) { {2, 3, false}, } { org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: testCase.OrgID}) - isOwner, err := org.IsOwnedBy(testCase.UserID) + isOwner, err := org.IsOwnedBy(db.DefaultContext, testCase.UserID) assert.NoError(t, err) assert.Equal(t, testCase.ExpectedOwner, isOwner) } @@ -52,7 +52,7 @@ func TestUser_IsOrgMember(t *testing.T) { {2, 3, false}, } { org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: testCase.OrgID}) - isMember, err := org.IsOrgMember(testCase.UserID) + isMember, err := org.IsOrgMember(db.DefaultContext, testCase.UserID) assert.NoError(t, err) assert.Equal(t, testCase.ExpectedMember, isMember) } @@ -89,7 +89,7 @@ func TestUser_GetOwnerTeam(t *testing.T) { func TestUser_GetTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - teams, err := org.LoadTeams() + teams, err := org.LoadTeams(db.DefaultContext) assert.NoError(t, err) if assert.Len(t, teams, 5) { assert.Equal(t, int64(1), teams[0].ID) @@ -103,7 +103,7 @@ func TestUser_GetTeams(t *testing.T) { func TestUser_GetMembers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - members, _, err := org.GetMembers() + members, _, err := org.GetMembers(db.DefaultContext) assert.NoError(t, err) if assert.Len(t, members, 3) { assert.Equal(t, int64(2), members[0].ID) @@ -115,10 +115,10 @@ func TestUser_GetMembers(t *testing.T) { func TestGetOrgByName(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - org, err := organization.GetOrgByName(db.DefaultContext, "user3") + org, err := organization.GetOrgByName(db.DefaultContext, "org3") assert.NoError(t, err) assert.EqualValues(t, 3, org.ID) - assert.Equal(t, "user3", org.Name) + assert.Equal(t, "org3", org.Name) _, err = organization.GetOrgByName(db.DefaultContext, "user2") // user2 is an individual assert.True(t, organization.IsErrOrgNotExist(err)) @@ -131,7 +131,7 @@ func TestCountOrganizations(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{}) assert.NoError(t, err) - cnt, err := organization.CountOrgs(organization.FindOrgOptions{IncludePrivate: true}) + cnt, err := organization.CountOrgs(db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true}) assert.NoError(t, err) assert.Equal(t, expected, cnt) } @@ -168,7 +168,7 @@ func TestIsOrganizationMember(t *testing.T) { func TestIsPublicMembership(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(orgID, userID int64, expected bool) { - isMember, err := organization.IsPublicMembership(orgID, userID) + isMember, err := organization.IsPublicMembership(db.DefaultContext, orgID, userID) assert.NoError(t, err) assert.EqualValues(t, expected, isMember) } @@ -183,7 +183,7 @@ func TestIsPublicMembership(t *testing.T) { func TestFindOrgs(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - orgs, err := organization.FindOrgs(organization.FindOrgOptions{ + orgs, err := organization.FindOrgs(db.DefaultContext, organization.FindOrgOptions{ UserID: 4, IncludePrivate: true, }) @@ -192,14 +192,14 @@ func TestFindOrgs(t *testing.T) { assert.EqualValues(t, 3, orgs[0].ID) } - orgs, err = organization.FindOrgs(organization.FindOrgOptions{ + orgs, err = organization.FindOrgs(db.DefaultContext, organization.FindOrgOptions{ UserID: 4, IncludePrivate: false, }) assert.NoError(t, err) assert.Len(t, orgs, 0) - total, err := organization.CountOrgs(organization.FindOrgOptions{ + total, err := organization.CountOrgs(db.DefaultContext, organization.FindOrgOptions{ UserID: 4, IncludePrivate: true, }) @@ -250,7 +250,7 @@ func TestChangeOrgUserStatus(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(orgID, userID int64, public bool) { - assert.NoError(t, organization.ChangeOrgUserStatus(orgID, userID, public)) + assert.NoError(t, organization.ChangeOrgUserStatus(db.DefaultContext, orgID, userID, public)) orgUser := unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: orgID, UID: userID}) assert.Equal(t, public, orgUser.IsPublic) } @@ -258,14 +258,14 @@ func TestChangeOrgUserStatus(t *testing.T) { testSuccess(3, 2, false) testSuccess(3, 2, false) testSuccess(3, 4, true) - assert.NoError(t, organization.ChangeOrgUserStatus(unittest.NonexistentID, unittest.NonexistentID, true)) + assert.NoError(t, organization.ChangeOrgUserStatus(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID, true)) } func TestUser_GetUserTeamIDs(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) testSuccess := func(userID int64, expected []int64) { - teamIDs, err := org.GetUserTeamIDs(userID) + teamIDs, err := org.GetUserTeamIDs(db.DefaultContext, userID) assert.NoError(t, err) assert.Equal(t, expected, teamIDs) } @@ -343,7 +343,7 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { func TestHasOrgVisibleTypePublic(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) const newOrgName = "test-org-public" org := &organization.Organization{ @@ -352,11 +352,11 @@ func TestHasOrgVisibleTypePublic(t *testing.T) { } unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(org, owner)) + assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, owner)) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner) - test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3) + test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), org3) test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil) assert.True(t, test1) // owner of org assert.True(t, test2) // user not a part of org @@ -366,7 +366,7 @@ func TestHasOrgVisibleTypePublic(t *testing.T) { func TestHasOrgVisibleTypeLimited(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) const newOrgName = "test-org-limited" org := &organization.Organization{ @@ -375,11 +375,11 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) { } unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(org, owner)) + assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, owner)) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner) - test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3) + test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), org3) test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil) assert.True(t, test1) // owner of org assert.True(t, test2) // user not a part of org @@ -389,7 +389,7 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) { func TestHasOrgVisibleTypePrivate(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) const newOrgName = "test-org-private" org := &organization.Organization{ @@ -398,11 +398,11 @@ func TestHasOrgVisibleTypePrivate(t *testing.T) { } unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(org, owner)) + assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, owner)) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner) - test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3) + test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), org3) test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil) assert.True(t, test1) // owner of org assert.False(t, test2) // user not a part of org @@ -461,7 +461,7 @@ func TestCreateOrganization(t *testing.T) { } unittest.AssertNotExistsBean(t, &user_model.User{Name: newOrgName, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(org, owner)) + assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, owner)) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) ownerTeam := unittest.AssertExistsAndLoadBean(t, @@ -481,7 +481,7 @@ func TestCreateOrganization2(t *testing.T) { } unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) - err := organization.CreateOrganization(org, owner) + err := organization.CreateOrganization(db.DefaultContext, org, owner) assert.Error(t, err) assert.True(t, organization.IsErrUserNotAllowedCreateOrg(err)) unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) @@ -493,9 +493,9 @@ func TestCreateOrganization3(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := &organization.Organization{Name: "user3"} // should already exist + org := &organization.Organization{Name: "org3"} // should already exist unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check - err := organization.CreateOrganization(org, owner) + err := organization.CreateOrganization(db.DefaultContext, org, owner) assert.Error(t, err) assert.True(t, user_model.IsErrUserAlreadyExist(err)) unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) @@ -506,7 +506,7 @@ func TestCreateOrganization4(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - err := organization.CreateOrganization(&organization.Organization{Name: "assets"}, owner) + err := organization.CreateOrganization(db.DefaultContext, &organization.Organization{Name: "assets"}, owner) assert.Error(t, err) assert.True(t, db.IsErrNameReserved(err)) unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{}) diff --git a/models/organization/org_user.go b/models/organization/org_user.go index d0598ab5d1..5fe3a178d2 100644 --- a/models/organization/org_user.go +++ b/models/organization/org_user.go @@ -78,8 +78,8 @@ func IsOrganizationMember(ctx context.Context, orgID, uid int64) (bool, error) { } // IsPublicMembership returns true if the given user's membership of given org is public. -func IsPublicMembership(orgID, uid int64) (bool, error) { - return db.GetEngine(db.DefaultContext). +func IsPublicMembership(ctx context.Context, orgID, uid int64) (bool, error) { + return db.GetEngine(ctx). Where("uid=?", uid). And("org_id=?", orgID). And("is_public=?", true). @@ -98,12 +98,12 @@ func CanCreateOrgRepo(ctx context.Context, orgID, uid int64) (bool, error) { } // IsUserOrgOwner returns true if user is in the owner team of given organization. -func IsUserOrgOwner(users user_model.UserList, orgID int64) map[int64]bool { +func IsUserOrgOwner(ctx context.Context, users user_model.UserList, orgID int64) map[int64]bool { results := make(map[int64]bool, len(users)) for _, user := range users { results[user.ID] = false // Set default to false } - ownerMaps, err := loadOrganizationOwners(db.DefaultContext, users, orgID) + ownerMaps, err := loadOrganizationOwners(ctx, users, orgID) if err == nil { for _, owner := range ownerMaps { results[owner.UID] = true diff --git a/models/organization/org_user_test.go b/models/organization/org_user_test.go index b6477f859c..7924517f31 100644 --- a/models/organization/org_user_test.go +++ b/models/organization/org_user_test.go @@ -39,7 +39,7 @@ func TestUserIsPublicMember(t *testing.T) { func testUserIsPublicMember(t *testing.T, uid, orgID int64, expected bool) { user, err := user_model.GetUserByID(db.DefaultContext, uid) assert.NoError(t, err) - is, err := organization.IsPublicMembership(orgID, user.ID) + is, err := organization.IsPublicMembership(db.DefaultContext, orgID, user.ID) assert.NoError(t, err) assert.Equal(t, expected, is) } @@ -94,7 +94,7 @@ func TestUserListIsPublicMember(t *testing.T) { func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) { org, err := organization.GetOrgByID(db.DefaultContext, orgID) assert.NoError(t, err) - _, membersIsPublic, err := org.GetMembers() + _, membersIsPublic, err := org.GetMembers(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, expected, membersIsPublic) } @@ -121,9 +121,9 @@ func TestUserListIsUserOrgOwner(t *testing.T) { func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) { org, err := organization.GetOrgByID(db.DefaultContext, orgID) assert.NoError(t, err) - members, _, err := org.GetMembers() + members, _, err := org.GetMembers(db.DefaultContext) assert.NoError(t, err) - assert.Equal(t, expected, organization.IsUserOrgOwner(members, orgID)) + assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID)) } func TestAddOrgUser(t *testing.T) { @@ -134,7 +134,7 @@ func TestAddOrgUser(t *testing.T) { if !unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) { expectedNumMembers++ } - assert.NoError(t, organization.AddOrgUser(orgID, userID)) + assert.NoError(t, organization.AddOrgUser(db.DefaultContext, orgID, userID)) ou := &organization.OrgUser{OrgID: orgID, UID: userID} unittest.AssertExistsAndLoadBean(t, ou) assert.Equal(t, isPublic, ou.IsPublic) diff --git a/models/organization/team.go b/models/organization/team.go index 3624fdc466..72afc67369 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -144,8 +144,8 @@ func (t *Team) IsOwnerTeam() bool { } // IsMember returns true if given user is a member of team. -func (t *Team) IsMember(userID int64) bool { - isMember, err := IsTeamMember(db.DefaultContext, t.OrgID, t.ID, userID) +func (t *Team) IsMember(ctx context.Context, userID int64) bool { + isMember, err := IsTeamMember(ctx, t.OrgID, t.ID, userID) if err != nil { log.Error("IsMember: %v", err) return false @@ -217,10 +217,10 @@ func GetTeam(ctx context.Context, orgID int64, name string) (*Team, error) { } // GetTeamIDsByNames returns a slice of team ids corresponds to names. -func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) { +func GetTeamIDsByNames(ctx context.Context, orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) { ids := make([]int64, 0, len(names)) for _, name := range names { - u, err := GetTeam(db.DefaultContext, orgID, name) + u, err := GetTeam(ctx, orgID, name) if err != nil { if ignoreNonExistent { continue @@ -251,13 +251,13 @@ func GetTeamByID(ctx context.Context, teamID int64) (*Team, error) { } // GetTeamNamesByID returns team's lower name from a list of team ids. -func GetTeamNamesByID(teamIDs []int64) ([]string, error) { +func GetTeamNamesByID(ctx context.Context, teamIDs []int64) ([]string, error) { if len(teamIDs) == 0 { return []string{}, nil } var teamNames []string - err := db.GetEngine(db.DefaultContext).Table("team"). + err := db.GetEngine(ctx).Table("team"). Select("lower_name"). In("id", teamIDs). Asc("name"). diff --git a/models/organization/team_invite_test.go b/models/organization/team_invite_test.go index cd6e1fe2ef..45db8494e8 100644 --- a/models/organization/team_invite_test.go +++ b/models/organization/team_invite_test.go @@ -30,12 +30,12 @@ func TestTeamInvite(t *testing.T) { t.Run("CreateAndRemove", func(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - invite, err := organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com") + invite, err := organization.CreateTeamInvite(db.DefaultContext, user1, team, "org3@example.com") assert.NotNil(t, invite) assert.NoError(t, err) // Shouldn't allow duplicate invite - _, err = organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com") + _, err = organization.CreateTeamInvite(db.DefaultContext, user1, team, "org3@example.com") assert.Error(t, err) // should remove invite diff --git a/models/organization/team_list.go b/models/organization/team_list.go index efb3104ad5..5b45429acf 100644 --- a/models/organization/team_list.go +++ b/models/organization/team_list.go @@ -77,8 +77,8 @@ func (opts *SearchTeamOptions) toCond() builder.Cond { } // SearchTeam search for teams. Caller is responsible to check permissions. -func SearchTeam(opts *SearchTeamOptions) (TeamList, int64, error) { - sess := db.GetEngine(db.DefaultContext) +func SearchTeam(ctx context.Context, opts *SearchTeamOptions) (TeamList, int64, error) { + sess := db.GetEngine(ctx) opts.SetDefaultValues() cond := opts.toCond() diff --git a/models/organization/team_test.go b/models/organization/team_test.go index c63b83aab7..23a6affe24 100644 --- a/models/organization/team_test.go +++ b/models/organization/team_test.go @@ -27,14 +27,14 @@ func TestTeam_IsMember(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}) - assert.True(t, team.IsMember(2)) - assert.False(t, team.IsMember(4)) - assert.False(t, team.IsMember(unittest.NonexistentID)) + assert.True(t, team.IsMember(db.DefaultContext, 2)) + assert.False(t, team.IsMember(db.DefaultContext, 4)) + assert.False(t, team.IsMember(db.DefaultContext, unittest.NonexistentID)) team = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - assert.True(t, team.IsMember(2)) - assert.True(t, team.IsMember(4)) - assert.False(t, team.IsMember(unittest.NonexistentID)) + assert.True(t, team.IsMember(db.DefaultContext, 2)) + assert.True(t, team.IsMember(db.DefaultContext, 4)) + assert.False(t, team.IsMember(db.DefaultContext, unittest.NonexistentID)) } func TestTeam_GetRepositories(t *testing.T) { @@ -142,7 +142,7 @@ func TestGetTeamMembers(t *testing.T) { func TestGetUserTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(userID int64) { - teams, _, err := organization.SearchTeam(&organization.SearchTeamOptions{UserID: userID}) + teams, _, err := organization.SearchTeam(db.DefaultContext, &organization.SearchTeamOptions{UserID: userID}) assert.NoError(t, err) for _, team := range teams { unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{TeamID: team.ID, UID: userID}) @@ -188,7 +188,7 @@ func TestUsersInTeamsCount(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(teamIDs, userIDs []int64, expected int64) { - count, err := organization.UsersInTeamsCount(teamIDs, userIDs) + count, err := organization.UsersInTeamsCount(db.DefaultContext, teamIDs, userIDs) assert.NoError(t, err) assert.Equal(t, expected, count) } diff --git a/models/organization/team_unit.go b/models/organization/team_unit.go index 7668ca7483..3087b70770 100644 --- a/models/organization/team_unit.go +++ b/models/organization/team_unit.go @@ -30,8 +30,8 @@ func getUnitsByTeamID(ctx context.Context, teamID int64) (units []*TeamUnit, err } // UpdateTeamUnits updates a teams's units -func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateTeamUnits(ctx context.Context, team *Team, units []TeamUnit) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/organization/team_user.go b/models/organization/team_user.go index 768dc24c50..ab767db200 100644 --- a/models/organization/team_user.go +++ b/models/organization/team_user.go @@ -78,9 +78,9 @@ func IsUserInTeams(ctx context.Context, userID int64, teamIDs []int64) (bool, er } // UsersInTeamsCount counts the number of users which are in userIDs and teamIDs -func UsersInTeamsCount(userIDs, teamIDs []int64) (int64, error) { +func UsersInTeamsCount(ctx context.Context, userIDs, teamIDs []int64) (int64, error) { var ids []int64 - if err := db.GetEngine(db.DefaultContext).In("uid", userIDs).In("team_id", teamIDs). + if err := db.GetEngine(ctx).In("uid", userIDs).In("team_id", teamIDs). Table("team_user"). Cols("uid").GroupBy("uid").Find(&ids); err != nil { return 0, err diff --git a/models/packages/package_test.go b/models/packages/package_test.go index 525a9f08d6..7f03151e77 100644 --- a/models/packages/package_test.go +++ b/models/packages/package_test.go @@ -4,7 +4,6 @@ package packages_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/db" @@ -20,9 +19,7 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } func TestHasOwnerPackages(t *testing.T) { diff --git a/models/perm/access/main_test.go b/models/perm/access/main_test.go index 8102cae496..0a350dc41e 100644 --- a/models/perm/access/main_test.go +++ b/models/perm/access/main_test.go @@ -4,7 +4,6 @@ package access_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -17,7 +16,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", "..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 2027b87ecb..395ecdf1a5 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -261,16 +261,16 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } // IsUserRealRepoAdmin check if this user is real repo admin -func IsUserRealRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bool, error) { +func IsUserRealRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) { if repo.OwnerID == user.ID { return true, nil } - if err := repo.LoadOwner(db.DefaultContext); err != nil { + if err := repo.LoadOwner(ctx); err != nil { return false, err } - accessMode, err := accessLevel(db.DefaultContext, user, repo) + accessMode, err := accessLevel(ctx, user, repo) if err != nil { return false, err } @@ -394,13 +394,13 @@ func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mo } // GetRepoReaders returns all users that have explicit read access or higher to the repository. -func GetRepoReaders(repo *repo_model.Repository) (_ []*user_model.User, err error) { - return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeRead) +func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { + return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead) } // GetRepoWriters returns all users that have write access to the repository. -func GetRepoWriters(repo *repo_model.Repository) (_ []*user_model.User, err error) { - return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeWrite) +func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { + return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite) } // IsRepoReader returns true if user has explicit read access or higher to the repository. diff --git a/models/project/board.go b/models/project/board.go index 2ab1d5c7b5..3e2d8e0472 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -69,8 +69,8 @@ func (Board) TableName() string { } // NumIssues return counter of all issues assigned to the board -func (b *Board) NumIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (b *Board) NumIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Where("project_id=?", b.ProjectID). And("project_board_id=?", b.ID). GroupBy("issue_id"). @@ -142,18 +142,18 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error { } // NewBoard adds a new project board to a given project -func NewBoard(board *Board) error { +func NewBoard(ctx context.Context, board *Board) error { if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) { return fmt.Errorf("bad color code: %s", board.Color) } - _, err := db.GetEngine(db.DefaultContext).Insert(board) + _, err := db.GetEngine(ctx).Insert(board) return err } // DeleteBoardByID removes all issues references to the project board. -func DeleteBoardByID(boardID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteBoardByID(ctx context.Context, boardID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -265,8 +265,8 @@ func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) { // SetDefaultBoard represents a board for issues not assigned to one // if boardID is 0 unset default -func SetDefaultBoard(projectID, boardID int64) error { - _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{ +func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error { + _, err := db.GetEngine(ctx).Where(builder.Eq{ "project_id": projectID, "`default`": true, }).Cols("`default`").Update(&Board{Default: false}) @@ -275,7 +275,7 @@ func SetDefaultBoard(projectID, boardID int64) error { } if boardID > 0 { - _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}). + _, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}). Cols("`default`").Update(&Board{Default: true}) } @@ -283,9 +283,9 @@ func SetDefaultBoard(projectID, boardID int64) error { } // UpdateBoardSorting update project board sorting -func UpdateBoardSorting(bs BoardList) error { +func UpdateBoardSorting(ctx context.Context, bs BoardList) error { for i := range bs { - _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols( + _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( "sorting", ).Update(bs[i]) if err != nil { diff --git a/models/project/issue.go b/models/project/issue.go index 3269197d6c..ebc9719de5 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -34,8 +34,8 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error } // NumIssues return counter of all issues assigned to a project -func (p *Project) NumIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (p *Project) NumIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Where("project_id=?", p.ID). GroupBy("issue_id"). Cols("issue_id"). @@ -48,8 +48,8 @@ func (p *Project) NumIssues() int { } // NumClosedIssues return counter of closed issues assigned to a project -func (p *Project) NumClosedIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (p *Project) NumClosedIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Join("INNER", "issue", "project_issue.issue_id=issue.id"). Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true). Cols("issue_id"). @@ -62,8 +62,8 @@ func (p *Project) NumClosedIssues() int { } // NumOpenIssues return counter of open issues assigned to a project -func (p *Project) NumOpenIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (p *Project) NumOpenIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Join("INNER", "issue", "project_issue.issue_id=issue.id"). Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false). Cols("issue_id"). @@ -76,8 +76,8 @@ func (p *Project) NumOpenIssues() int { } // MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column -func MoveIssuesOnProjectBoard(board *Board, sortedIssueIDs map[int64]int64) error { - return db.WithTx(db.DefaultContext, func(ctx context.Context) error { +func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { sess := db.GetEngine(ctx) issueIDs := make([]int64, 0, len(sortedIssueIDs)) diff --git a/models/project/main_test.go b/models/project/main_test.go index 816cbeb94a..f4b2d6feda 100644 --- a/models/project/main_test.go +++ b/models/project/main_test.go @@ -4,7 +4,6 @@ package project import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -14,7 +13,6 @@ import ( func TestMain(m *testing.M) { unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), FixtureFiles: []string{ "project.yml", "project_board.yml", diff --git a/models/project/project.go b/models/project/project.go index 8056aba252..3a1bfe1dbd 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -124,9 +124,9 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) { } // Link returns the project's relative URL. -func (p *Project) Link() string { +func (p *Project) Link(ctx context.Context) string { if p.OwnerID > 0 { - err := p.LoadOwner(db.DefaultContext) + err := p.LoadOwner(ctx) if err != nil { log.Error("LoadOwner: %v", err) return "" @@ -134,7 +134,7 @@ func (p *Project) Link() string { return fmt.Sprintf("%s/-/projects/%d", p.Owner.HomeLink(), p.ID) } if p.RepoID > 0 { - err := p.LoadRepo(db.DefaultContext) + err := p.LoadRepo(ctx) if err != nil { log.Error("LoadRepo: %v", err) return "" @@ -261,7 +261,7 @@ func FindProjects(ctx context.Context, opts SearchOptions) ([]*Project, int64, e } // NewProject creates a new Project -func NewProject(p *Project) error { +func NewProject(ctx context.Context, p *Project) error { if !IsBoardTypeValid(p.BoardType) { p.BoardType = BoardTypeNone } @@ -274,7 +274,7 @@ func NewProject(p *Project) error { return util.NewInvalidArgumentErrorf("project type is not valid") } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -348,8 +348,8 @@ func updateRepositoryProjectCount(ctx context.Context, repoID int64) error { } // ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed -func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeProjectStatusByRepoIDAndID(ctx context.Context, repoID, projectID int64, isClosed bool) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -372,8 +372,8 @@ func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) er } // ChangeProjectStatus toggle a project between opened and closed -func ChangeProjectStatus(p *Project, isClosed bool) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeProjectStatus(ctx context.Context, p *Project, isClosed bool) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/project/project_test.go b/models/project/project_test.go index d1a4715653..6b5bd5b371 100644 --- a/models/project/project_test.go +++ b/models/project/project_test.go @@ -60,7 +60,7 @@ func TestProject(t *testing.T) { CreatorID: 2, } - assert.NoError(t, NewProject(project)) + assert.NoError(t, NewProject(db.DefaultContext, project)) _, err := GetProjectByID(db.DefaultContext, project.ID) assert.NoError(t, err) @@ -74,7 +74,7 @@ func TestProject(t *testing.T) { assert.Equal(t, project.Title, projectFromDB.Title) - assert.NoError(t, ChangeProjectStatus(project, true)) + assert.NoError(t, ChangeProjectStatus(db.DefaultContext, project, true)) // Retrieve from DB afresh to check if it is truly closed projectFromDB, err = GetProjectByID(db.DefaultContext, project.ID) diff --git a/models/repo.go b/models/repo.go index de0f7ee345..d525264b3b 100644 --- a/models/repo.go +++ b/models/repo.go @@ -16,7 +16,6 @@ import ( issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -24,10 +23,7 @@ import ( // Init initialize model func Init(ctx context.Context) error { - if err := unit.LoadUnitConfig(); err != nil { - return err - } - return system_model.Init(ctx) + return unit.LoadUnitConfig() } type repoChecker struct { @@ -281,8 +277,8 @@ func UpdateRepoStats(ctx context.Context, id int64) error { return nil } -func updateUserStarNumbers(users []user_model.User) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func updateUserStarNumbers(ctx context.Context, users []user_model.User) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -298,19 +294,19 @@ func updateUserStarNumbers(users []user_model.User) error { } // DoctorUserStarNum recalculate Stars number for all user -func DoctorUserStarNum() (err error) { +func DoctorUserStarNum(ctx context.Context) (err error) { const batchSize = 100 for start := 0; ; start += batchSize { users := make([]user_model.User, 0, batchSize) - if err = db.GetEngine(db.DefaultContext).Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil { + if err = db.GetEngine(ctx).Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil { return err } if len(users) == 0 { break } - if err = updateUserStarNumbers(users); err != nil { + if err = updateUserStarNumbers(ctx, users); err != nil { return err } } diff --git a/models/repo/archiver.go b/models/repo/archiver.go index 70f53cfe15..6d0ed42877 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -72,7 +72,7 @@ var delRepoArchiver = new(RepoArchiver) // DeleteRepoArchiver delete archiver func DeleteRepoArchiver(ctx context.Context, archiver *RepoArchiver) error { - _, err := db.GetEngine(db.DefaultContext).ID(archiver.ID).Delete(delRepoArchiver) + _, err := db.GetEngine(ctx).ID(archiver.ID).Delete(delRepoArchiver) return err } @@ -113,8 +113,8 @@ func UpdateRepoArchiverStatus(ctx context.Context, archiver *RepoArchiver) error } // DeleteAllRepoArchives deletes all repo archives records -func DeleteAllRepoArchives() error { - _, err := db.GetEngine(db.DefaultContext).Where("1=1").Delete(new(RepoArchiver)) +func DeleteAllRepoArchives(ctx context.Context) error { + _, err := db.GetEngine(ctx).Where("1=1").Delete(new(RepoArchiver)) return err } @@ -133,10 +133,10 @@ func (opts FindRepoArchiversOption) toConds() builder.Cond { } // FindRepoArchives find repo archivers -func FindRepoArchives(opts FindRepoArchiversOption) ([]*RepoArchiver, error) { +func FindRepoArchives(ctx context.Context, opts FindRepoArchiversOption) ([]*RepoArchiver, error) { archivers := make([]*RepoArchiver, 0, opts.PageSize) start, limit := opts.GetSkipTake() - err := db.GetEngine(db.DefaultContext).Where(opts.toConds()). + err := db.GetEngine(ctx).Where(opts.toConds()). Asc("created_unix"). Limit(limit, start). Find(&archivers) @@ -144,7 +144,7 @@ func FindRepoArchives(opts FindRepoArchiversOption) ([]*RepoArchiver, error) { } // SetArchiveRepoState sets if a repo is archived -func SetArchiveRepoState(repo *Repository, isArchived bool) (err error) { +func SetArchiveRepoState(ctx context.Context, repo *Repository, isArchived bool) (err error) { repo.IsArchived = isArchived if isArchived { @@ -153,6 +153,6 @@ func SetArchiveRepoState(repo *Repository, isArchived bool) (err error) { repo.ArchivedUnix = timeutil.TimeStamp(0) } - _, err = db.GetEngine(db.DefaultContext).ID(repo.ID).Cols("is_archived", "archived_unix").NoAutoTime().Update(repo) + _, err = db.GetEngine(ctx).ID(repo.ID).Cols("is_archived", "archived_unix").NoAutoTime().Update(repo) return err } diff --git a/models/repo/attachment.go b/models/repo/attachment.go index df3b9cd213..1a588398c1 100644 --- a/models/repo/attachment.go +++ b/models/repo/attachment.go @@ -37,9 +37,9 @@ func init() { } // IncreaseDownloadCount is update download count + 1 -func (a *Attachment) IncreaseDownloadCount() error { +func (a *Attachment) IncreaseDownloadCount(ctx context.Context) error { // Update download count. - if _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil { + if _, err := db.GetEngine(ctx).Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil { return fmt.Errorf("increase attachment count: %w", err) } @@ -164,8 +164,8 @@ func GetAttachmentByReleaseIDFileName(ctx context.Context, releaseID int64, file } // DeleteAttachment deletes the given attachment and optionally the associated file. -func DeleteAttachment(a *Attachment, remove bool) error { - _, err := DeleteAttachments(db.DefaultContext, []*Attachment{a}, remove) +func DeleteAttachment(ctx context.Context, a *Attachment, remove bool) error { + _, err := DeleteAttachments(ctx, []*Attachment{a}, remove) return err } @@ -196,23 +196,23 @@ func DeleteAttachments(ctx context.Context, attachments []*Attachment, remove bo } // DeleteAttachmentsByIssue deletes all attachments associated with the given issue. -func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) { - attachments, err := GetAttachmentsByIssueID(db.DefaultContext, issueID) +func DeleteAttachmentsByIssue(ctx context.Context, issueID int64, remove bool) (int, error) { + attachments, err := GetAttachmentsByIssueID(ctx, issueID) if err != nil { return 0, err } - return DeleteAttachments(db.DefaultContext, attachments, remove) + return DeleteAttachments(ctx, attachments, remove) } // DeleteAttachmentsByComment deletes all attachments associated with the given comment. -func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) { - attachments, err := GetAttachmentsByCommentID(db.DefaultContext, commentID) +func DeleteAttachmentsByComment(ctx context.Context, commentID int64, remove bool) (int, error) { + attachments, err := GetAttachmentsByCommentID(ctx, commentID) if err != nil { return 0, err } - return DeleteAttachments(db.DefaultContext, attachments, remove) + return DeleteAttachments(ctx, attachments, remove) } // UpdateAttachmentByUUID Updates attachment via uuid diff --git a/models/repo/attachment_test.go b/models/repo/attachment_test.go index 21fba227a5..c059ffd39a 100644 --- a/models/repo/attachment_test.go +++ b/models/repo/attachment_test.go @@ -21,7 +21,7 @@ func TestIncreaseDownloadCount(t *testing.T) { assert.Equal(t, int64(0), attachment.DownloadCount) // increase download count - err = attachment.IncreaseDownloadCount() + err = attachment.IncreaseDownloadCount(db.DefaultContext) assert.NoError(t, err) attachment, err = repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") @@ -45,15 +45,15 @@ func TestGetByCommentOrIssueID(t *testing.T) { func TestDeleteAttachments(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - count, err := repo_model.DeleteAttachmentsByIssue(4, false) + count, err := repo_model.DeleteAttachmentsByIssue(db.DefaultContext, 4, false) assert.NoError(t, err) assert.Equal(t, 2, count) - count, err = repo_model.DeleteAttachmentsByComment(2, false) + count, err = repo_model.DeleteAttachmentsByComment(db.DefaultContext, 2, false) assert.NoError(t, err) assert.Equal(t, 2, count) - err = repo_model.DeleteAttachment(&repo_model.Attachment{ID: 8}, false) + err = repo_model.DeleteAttachment(db.DefaultContext, &repo_model.Attachment{ID: 8}, false) assert.NoError(t, err) attachment, err := repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18") diff --git a/models/repo/avatar.go b/models/repo/avatar.go index a76a949267..72ee938ada 100644 --- a/models/repo/avatar.go +++ b/models/repo/avatar.go @@ -31,8 +31,8 @@ func ExistsWithAvatarAtStoragePath(ctx context.Context, storagePath string) (boo } // RelAvatarLink returns a relative link to the repository's avatar. -func (repo *Repository) RelAvatarLink() string { - return repo.relAvatarLink(db.DefaultContext) +func (repo *Repository) RelAvatarLink(ctx context.Context) string { + return repo.relAvatarLink(ctx) } // generateRandomAvatar generates a random avatar for repository. diff --git a/models/repo/collaboration.go b/models/repo/collaboration.go index 7989e5bdf9..2018ae2a7d 100644 --- a/models/repo/collaboration.go +++ b/models/repo/collaboration.go @@ -62,8 +62,8 @@ func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOpti } // CountCollaborators returns total number of collaborators for a repository -func CountCollaborators(repoID int64) (int64, error) { - return db.GetEngine(db.DefaultContext).Where("repo_id = ? ", repoID).Count(&Collaboration{}) +func CountCollaborators(ctx context.Context, repoID int64) (int64, error) { + return db.GetEngine(ctx).Where("repo_id = ? ", repoID).Count(&Collaboration{}) } // GetCollaboration get collaboration for a repository id with a user id @@ -138,11 +138,11 @@ func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid in } // IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository -func IsOwnerMemberCollaborator(repo *Repository, userID int64) (bool, error) { +func IsOwnerMemberCollaborator(ctx context.Context, repo *Repository, userID int64) (bool, error) { if repo.OwnerID == userID { return true, nil } - teamMember, err := db.GetEngine(db.DefaultContext).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). + teamMember, err := db.GetEngine(ctx).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id"). Where("team_repo.repo_id = ?", repo.ID). And("team_unit.`type` = ?", unit.TypeCode). @@ -154,5 +154,5 @@ func IsOwnerMemberCollaborator(repo *Repository, userID int64) (bool, error) { return true, nil } - return db.GetEngine(db.DefaultContext).Get(&Collaboration{RepoID: repo.ID, UserID: userID}) + return db.GetEngine(ctx).Get(&Collaboration{RepoID: repo.ID, UserID: userID}) } diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go index a29dfe99a5..38114c307f 100644 --- a/models/repo/collaboration_test.go +++ b/models/repo/collaboration_test.go @@ -89,17 +89,17 @@ func TestRepository_CountCollaborators(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - count, err := repo_model.CountCollaborators(repo1.ID) + count, err := repo_model.CountCollaborators(db.DefaultContext, repo1.ID) assert.NoError(t, err) assert.EqualValues(t, 2, count) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) - count, err = repo_model.CountCollaborators(repo2.ID) + count, err = repo_model.CountCollaborators(db.DefaultContext, repo2.ID) assert.NoError(t, err) assert.EqualValues(t, 2, count) // Non-existent repository. - count, err = repo_model.CountCollaborators(unittest.NonexistentID) + count, err = repo_model.CountCollaborators(db.DefaultContext, unittest.NonexistentID) assert.NoError(t, err) assert.EqualValues(t, 0, count) } @@ -110,31 +110,31 @@ func TestRepository_IsOwnerMemberCollaborator(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // Organisation owner. - actual, err := repo_model.IsOwnerMemberCollaborator(repo1, 2) + actual, err := repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo1, 2) assert.NoError(t, err) assert.True(t, actual) // Team member. - actual, err = repo_model.IsOwnerMemberCollaborator(repo1, 4) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo1, 4) assert.NoError(t, err) assert.True(t, actual) // Normal user. - actual, err = repo_model.IsOwnerMemberCollaborator(repo1, 1) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo1, 1) assert.NoError(t, err) assert.False(t, actual) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) // Collaborator. - actual, err = repo_model.IsOwnerMemberCollaborator(repo2, 4) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo2, 4) assert.NoError(t, err) assert.True(t, actual) repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) // Repository owner. - actual, err = repo_model.IsOwnerMemberCollaborator(repo3, 2) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo3, 2) assert.NoError(t, err) assert.True(t, actual) } diff --git a/models/repo/fork.go b/models/repo/fork.go index eafbab0fb1..6be6ebc3f5 100644 --- a/models/repo/fork.go +++ b/models/repo/fork.go @@ -21,9 +21,9 @@ func GetRepositoriesByForkID(ctx context.Context, forkID int64) ([]*Repository, } // GetForkedRepo checks if given user has already forked a repository with given ID. -func GetForkedRepo(ownerID, repoID int64) *Repository { +func GetForkedRepo(ctx context.Context, ownerID, repoID int64) *Repository { repo := new(Repository) - has, _ := db.GetEngine(db.DefaultContext). + has, _ := db.GetEngine(ctx). Where("owner_id=? AND fork_id=?", ownerID, repoID). Get(repo) if has { @@ -33,8 +33,8 @@ func GetForkedRepo(ownerID, repoID int64) *Repository { } // HasForkedRepo checks if given user has already forked a repository with given ID. -func HasForkedRepo(ownerID, repoID int64) bool { - has, _ := db.GetEngine(db.DefaultContext). +func HasForkedRepo(ctx context.Context, ownerID, repoID int64) bool { + has, _ := db.GetEngine(ctx). Table("repository"). Where("owner_id=? AND fork_id=?", ownerID, repoID). Exist() @@ -55,10 +55,10 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error) } // GetForks returns all the forks of the repository -func GetForks(repo *Repository, listOptions db.ListOptions) ([]*Repository, error) { +func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) { if listOptions.Page == 0 { forks := make([]*Repository, 0, repo.NumForks) - return forks, db.GetEngine(db.DefaultContext).Find(&forks, &Repository{ForkID: repo.ID}) + return forks, db.GetEngine(ctx).Find(&forks, &Repository{ForkID: repo.ID}) } sess := db.GetPaginatedSession(&listOptions) diff --git a/models/repo/git.go b/models/repo/git.go index c1af7ee960..610c554296 100644 --- a/models/repo/git.go +++ b/models/repo/git.go @@ -3,7 +3,11 @@ package repo -import "code.gitea.io/gitea/models/db" +import ( + "context" + + "code.gitea.io/gitea/models/db" +) // MergeStyle represents the approach to merge commits into base branch. type MergeStyle string @@ -24,7 +28,7 @@ const ( ) // UpdateDefaultBranch updates the default branch -func UpdateDefaultBranch(repo *Repository) error { - _, err := db.GetEngine(db.DefaultContext).ID(repo.ID).Cols("default_branch").Update(repo) +func UpdateDefaultBranch(ctx context.Context, repo *Repository) error { + _, err := db.GetEngine(ctx).ID(repo.ID).Cols("default_branch").Update(repo) return err } diff --git a/models/repo/language_stats.go b/models/repo/language_stats.go index 2da16814bd..0bc0f1fb40 100644 --- a/models/repo/language_stats.go +++ b/models/repo/language_stats.go @@ -108,8 +108,8 @@ func GetLanguageStats(ctx context.Context, repo *Repository) (LanguageStatList, } // GetTopLanguageStats returns the top language statistics for a repository -func GetTopLanguageStats(repo *Repository, limit int) (LanguageStatList, error) { - stats, err := GetLanguageStats(db.DefaultContext, repo) +func GetTopLanguageStats(ctx context.Context, repo *Repository, limit int) (LanguageStatList, error) { + stats, err := GetLanguageStats(ctx, repo) if err != nil { return nil, err } @@ -140,8 +140,8 @@ func GetTopLanguageStats(repo *Repository, limit int) (LanguageStatList, error) } // UpdateLanguageStats updates the language statistics for repository -func UpdateLanguageStats(repo *Repository, commitID string, stats map[string]int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateLanguageStats(ctx context.Context, repo *Repository, commitID string, stats map[string]int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -212,8 +212,8 @@ func UpdateLanguageStats(repo *Repository, commitID string, stats map[string]int } // CopyLanguageStat Copy originalRepo language stat information to destRepo (use for forked repo) -func CopyLanguageStat(originalRepo, destRepo *Repository) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CopyLanguageStat(ctx context.Context, originalRepo, destRepo *Repository) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/repo/main_test.go b/models/repo/main_test.go index ff97c7ac9e..b49855f2c8 100644 --- a/models/repo/main_test.go +++ b/models/repo/main_test.go @@ -4,7 +4,6 @@ package repo_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -18,7 +17,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/repo/mirror.go b/models/repo/mirror.go index 39482037b2..be7b785612 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -31,7 +31,7 @@ type Mirror struct { LFS bool `xorm:"lfs_enabled NOT NULL DEFAULT false"` LFSEndpoint string `xorm:"lfs_endpoint TEXT"` - Address string `xorm:"-"` + RemoteAddress string `xorm:"VARCHAR(2048)"` } func init() { @@ -47,12 +47,12 @@ func (m *Mirror) BeforeInsert() { } // GetRepository returns the repository. -func (m *Mirror) GetRepository() *Repository { +func (m *Mirror) GetRepository(ctx context.Context) *Repository { if m.Repo != nil { return m.Repo } var err error - m.Repo, err = GetRepositoryByID(db.DefaultContext, m.RepoID) + m.Repo, err = GetRepositoryByID(ctx, m.RepoID) if err != nil { log.Error("getRepositoryByID[%d]: %v", m.ID, err) } @@ -99,14 +99,14 @@ func TouchMirror(ctx context.Context, m *Mirror) error { } // DeleteMirrorByRepoID deletes a mirror by repoID -func DeleteMirrorByRepoID(repoID int64) error { - _, err := db.GetEngine(db.DefaultContext).Delete(&Mirror{RepoID: repoID}) +func DeleteMirrorByRepoID(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Delete(&Mirror{RepoID: repoID}) return err } // MirrorsIterate iterates all mirror repositories. -func MirrorsIterate(limit int, f func(idx int, bean any) error) error { - sess := db.GetEngine(db.DefaultContext). +func MirrorsIterate(ctx context.Context, limit int, f func(idx int, bean any) error) error { + sess := db.GetEngine(ctx). Where("next_update_unix<=?", time.Now().Unix()). And("next_update_unix!=0"). OrderBy("updated_unix ASC") diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go index 3edbcceb9b..61cf1849b0 100644 --- a/models/repo/pushmirror.go +++ b/models/repo/pushmirror.go @@ -20,10 +20,11 @@ var ErrPushMirrorNotExist = util.NewNotExistErrorf("PushMirror does not exist") // PushMirror represents mirror information of a repository. type PushMirror struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` - Repo *Repository `xorm:"-"` - RemoteName string + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + Repo *Repository `xorm:"-"` + RemoteName string + RemoteAddress string `xorm:"VARCHAR(2048)"` SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"` Interval time.Duration @@ -31,6 +32,7 @@ type PushMirror struct { LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"` LastError string `xorm:"text"` } + type PushMirrorOptions struct { ID int64 RepoID int64 @@ -56,12 +58,12 @@ func init() { } // GetRepository returns the path of the repository. -func (m *PushMirror) GetRepository() *Repository { +func (m *PushMirror) GetRepository(ctx context.Context) *Repository { if m.Repo != nil { return m.Repo } var err error - m.Repo, err = GetRepositoryByID(db.DefaultContext, m.RepoID) + m.Repo, err = GetRepositoryByID(ctx, m.RepoID) if err != nil { log.Error("getRepositoryByID[%d]: %v", m.ID, err) } diff --git a/models/repo/redirect.go b/models/repo/redirect.go index 92a011a453..61789ebefa 100644 --- a/models/repo/redirect.go +++ b/models/repo/redirect.go @@ -50,10 +50,10 @@ func init() { } // LookupRedirect look up if a repository has a redirect name -func LookupRedirect(ownerID int64, repoName string) (int64, error) { +func LookupRedirect(ctx context.Context, ownerID int64, repoName string) (int64, error) { repoName = strings.ToLower(repoName) redirect := &Redirect{OwnerID: ownerID, LowerName: repoName} - if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil { + if has, err := db.GetEngine(ctx).Get(redirect); err != nil { return 0, err } else if !has { return 0, ErrRedirectNotExist{OwnerID: ownerID, RepoName: repoName} diff --git a/models/repo/redirect_test.go b/models/repo/redirect_test.go index 8255704d59..24cf7e89fb 100644 --- a/models/repo/redirect_test.go +++ b/models/repo/redirect_test.go @@ -16,11 +16,11 @@ import ( func TestLookupRedirect(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repoID, err := repo_model.LookupRedirect(2, "oldrepo1") + repoID, err := repo_model.LookupRedirect(db.DefaultContext, 2, "oldrepo1") assert.NoError(t, err) assert.EqualValues(t, 1, repoID) - _, err = repo_model.LookupRedirect(unittest.NonexistentID, "doesnotexist") + _, err = repo_model.LookupRedirect(db.DefaultContext, unittest.NonexistentID, "doesnotexist") assert.True(t, repo_model.IsErrRedirectNotExist(err)) } diff --git a/models/repo/release.go b/models/repo/release.go index 0e92474365..ff31ec4510 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -181,9 +181,9 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs } // GetRelease returns release by given ID. -func GetRelease(repoID int64, tagName string) (*Release, error) { +func GetRelease(ctx context.Context, repoID int64, tagName string) (*Release, error) { rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)} - has, err := db.GetEngine(db.DefaultContext).Get(rel) + has, err := db.GetEngine(ctx).Get(rel) if err != nil { return nil, err } else if !has { @@ -284,12 +284,12 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) { } // CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID. -func CountReleasesByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) { - return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(new(Release)) +func CountReleasesByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.toConds(repoID)).Count(new(Release)) } // GetLatestReleaseByRepoID returns the latest release for a repository -func GetLatestReleaseByRepoID(repoID int64) (*Release, error) { +func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) { cond := builder.NewCond(). And(builder.Eq{"repo_id": repoID}). And(builder.Eq{"is_draft": false}). @@ -297,7 +297,7 @@ func GetLatestReleaseByRepoID(repoID int64) (*Release, error) { And(builder.Eq{"is_tag": false}) rel := new(Release) - has, err := db.GetEngine(db.DefaultContext). + has, err := db.GetEngine(ctx). Desc("created_unix", "id"). Where(cond). Get(rel) @@ -442,8 +442,8 @@ func DeleteReleaseByID(ctx context.Context, id int64) error { } // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID -func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("release"). +func UpdateReleasesMigrationsByType(ctx context.Context, gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("release"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). Update(map[string]any{ @@ -485,8 +485,8 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s } // PushUpdateDeleteTag must be called for any push actions to delete tag -func PushUpdateDeleteTag(repo *Repository, tagName string) error { - rel, err := GetRelease(repo.ID, tagName) +func PushUpdateDeleteTag(ctx context.Context, repo *Repository, tagName string) error { + rel, err := GetRelease(ctx, repo.ID, tagName) if err != nil { if IsErrReleaseNotExist(err) { return nil @@ -494,14 +494,14 @@ func PushUpdateDeleteTag(repo *Repository, tagName string) error { return fmt.Errorf("GetRelease: %w", err) } if rel.IsTag { - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil { + if _, err = db.GetEngine(ctx).ID(rel.ID).Delete(new(Release)); err != nil { return fmt.Errorf("Delete: %w", err) } } else { rel.IsDraft = true rel.NumCommits = 0 rel.Sha1 = "" - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { + if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil { return fmt.Errorf("Update: %w", err) } } @@ -510,15 +510,15 @@ func PushUpdateDeleteTag(repo *Repository, tagName string) error { } // SaveOrUpdateTag must be called for any push actions to add tag -func SaveOrUpdateTag(repo *Repository, newRel *Release) error { - rel, err := GetRelease(repo.ID, newRel.TagName) +func SaveOrUpdateTag(ctx context.Context, repo *Repository, newRel *Release) error { + rel, err := GetRelease(ctx, repo.ID, newRel.TagName) if err != nil && !IsErrReleaseNotExist(err) { return fmt.Errorf("GetRelease: %w", err) } if rel == nil { rel = newRel - if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil { + if _, err = db.GetEngine(ctx).Insert(rel); err != nil { return fmt.Errorf("InsertOne: %w", err) } } else { @@ -529,7 +529,7 @@ func SaveOrUpdateTag(repo *Repository, newRel *Release) error { if rel.IsTag && newRel.PublisherID > 0 { rel.PublisherID = newRel.PublisherID } - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { + if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil { return fmt.Errorf("Update: %w", err) } } @@ -554,8 +554,8 @@ func (r *Release) GetExternalName() string { return r.OriginalAuthor } func (r *Release) GetExternalID() int64 { return r.OriginalAuthorID } // InsertReleases migrates release -func InsertReleases(rels ...*Release) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func InsertReleases(ctx context.Context, rels ...*Release) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/repo/release_test.go b/models/repo/release_test.go index 2a45ab32f3..3643bff7f1 100644 --- a/models/repo/release_test.go +++ b/models/repo/release_test.go @@ -6,6 +6,7 @@ package repo import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -21,6 +22,6 @@ func TestMigrate_InsertReleases(t *testing.T) { Attachments: []*Attachment{a}, } - err := InsertReleases(r) + err := InsertReleases(db.DefaultContext, r) assert.NoError(t, err) } diff --git a/models/repo/repo.go b/models/repo/repo.go index b37948fea7..c4b215e074 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -47,6 +47,14 @@ func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error { return util.ErrPermissionDenied } +type ErrRepoIsArchived struct { + Repo *Repository +} + +func (err ErrRepoIsArchived) Error() string { + return fmt.Sprintf("%s is archived", err.Repo.LogString()) +} + var ( reservedRepoNames = []string{".", "..", "-"} reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} @@ -191,12 +199,8 @@ func (repo *Repository) SanitizedOriginalURL() string { if repo.OriginalURL == "" { return "" } - u, err := url.Parse(repo.OriginalURL) - if err != nil { - return "" - } - u.User = nil - return u.String() + u, _ := util.SanitizeURL(repo.OriginalURL) + return u } // text representations to be returned in SizeDetail.Name @@ -443,7 +447,7 @@ func (repo *Repository) MustOwner(ctx context.Context) *user_model.User { } // ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers. -func (repo *Repository) ComposeMetas() map[string]string { +func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string { if len(repo.RenderingMetas) == 0 { metas := map[string]string{ "user": repo.OwnerName, @@ -452,7 +456,7 @@ func (repo *Repository) ComposeMetas() map[string]string { "mode": "comment", } - unit, err := repo.GetUnit(db.DefaultContext, unit.TypeExternalTracker) + unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker) if err == nil { metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat switch unit.ExternalTrackerConfig().ExternalTrackerStyle { @@ -466,10 +470,10 @@ func (repo *Repository) ComposeMetas() map[string]string { } } - repo.MustOwner(db.DefaultContext) + repo.MustOwner(ctx) if repo.Owner.IsOrganization() { teams := make([]string, 0, 5) - _ = db.GetEngine(db.DefaultContext).Table("team_repo"). + _ = db.GetEngine(ctx).Table("team_repo"). Join("INNER", "team", "team.id = team_repo.team_id"). Where("team_repo.repo_id = ?", repo.ID). Select("team.lower_name"). @@ -485,10 +489,10 @@ func (repo *Repository) ComposeMetas() map[string]string { } // ComposeDocumentMetas composes a map of metas for properly rendering documents -func (repo *Repository) ComposeDocumentMetas() map[string]string { +func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string { if len(repo.DocumentRenderingMetas) == 0 { metas := map[string]string{} - for k, v := range repo.ComposeMetas() { + for k, v := range repo.ComposeMetas(ctx) { metas[k] = v } metas["mode"] = "document" @@ -562,8 +566,8 @@ func (repo *Repository) CanEnablePulls() bool { } // AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled. -func (repo *Repository) AllowsPulls() bool { - return repo.CanEnablePulls() && repo.UnitEnabled(db.DefaultContext, unit.TypePullRequests) +func (repo *Repository) AllowsPulls(ctx context.Context) bool { + return repo.CanEnablePulls() && repo.UnitEnabled(ctx, unit.TypePullRequests) } // CanEnableEditor returns true if repository meets the requirements of web editor. @@ -658,6 +662,14 @@ func (repo *Repository) GetTrustModel() TrustModelType { return trustModel } +// MustNotBeArchived returns ErrRepoIsArchived if the repo is archived +func (repo *Repository) MustNotBeArchived() error { + if repo.IsArchived { + return ErrRepoIsArchived{Repo: repo} + } + return nil +} + // __________ .__ __ // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | @@ -706,12 +718,12 @@ func GetRepositoryByOwnerAndName(ctx context.Context, ownerName, repoName string } // GetRepositoryByName returns the repository by given name under user if exists. -func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { +func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repository, error) { repo := &Repository{ OwnerID: ownerID, LowerName: strings.ToLower(name), } - has, err := db.GetEngine(db.DefaultContext).Get(repo) + has, err := db.GetEngine(ctx).Get(repo) if err != nil { return nil, err } else if !has { @@ -776,9 +788,9 @@ func GetRepositoryByID(ctx context.Context, id int64) (*Repository, error) { } // GetRepositoriesMapByIDs returns the repositories by given id slice. -func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { +func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repository, error) { repos := make(map[int64]*Repository, len(ids)) - return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos) + return repos, db.GetEngine(ctx).In("id", ids).Find(&repos) } // IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed. @@ -810,8 +822,8 @@ func GetTemplateRepo(ctx context.Context, repo *Repository) (*Repository, error) } // TemplateRepo returns the repository, which is template of this repository -func (repo *Repository) TemplateRepo() *Repository { - repo, err := GetTemplateRepo(db.DefaultContext, repo) +func (repo *Repository) TemplateRepo(ctx context.Context) *Repository { + repo, err := GetTemplateRepo(ctx, repo) if err != nil { log.Error("TemplateRepo: %v", err) return nil diff --git a/models/repo/repo_indexer.go b/models/repo/repo_indexer.go index 9667c56244..bad1248b40 100644 --- a/models/repo/repo_indexer.go +++ b/models/repo/repo_indexer.go @@ -36,14 +36,14 @@ func init() { } // GetUnindexedRepos returns repos which do not have an indexer status -func GetUnindexedRepos(indexerType RepoIndexerType, maxRepoID int64, page, pageSize int) ([]int64, error) { +func GetUnindexedRepos(ctx context.Context, indexerType RepoIndexerType, maxRepoID int64, page, pageSize int) ([]int64, error) { ids := make([]int64, 0, 50) cond := builder.Cond(builder.IsNull{ "repo_indexer_status.id", }).And(builder.Eq{ "repository.is_empty": false, }) - sess := db.GetEngine(db.DefaultContext).Table("repository").Join("LEFT OUTER", "repo_indexer_status", "repository.id = repo_indexer_status.repo_id AND repo_indexer_status.indexer_type = ?", indexerType) + sess := db.GetEngine(ctx).Table("repository").Join("LEFT OUTER", "repo_indexer_status", "repository.id = repo_indexer_status.repo_id AND repo_indexer_status.indexer_type = ?", indexerType) if maxRepoID > 0 { cond = builder.And(cond, builder.Lte{ "repository.id": maxRepoID, diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index a0485ed8d4..1668c23c77 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -21,8 +21,8 @@ import ( ) // FindReposMapByIDs find repos as map -func FindReposMapByIDs(repoIDs []int64, res map[int64]*Repository) error { - return db.GetEngine(db.DefaultContext).In("id", repoIDs).Find(&res) +func FindReposMapByIDs(ctx context.Context, repoIDs []int64, res map[int64]*Repository) error { + return db.GetEngine(ctx).In("id", repoIDs).Find(&res) } // RepositoryListDefaultPageSize is the default number of repositories @@ -672,12 +672,12 @@ func SearchRepositoryByName(ctx context.Context, opts *SearchRepoOptions) (Repos // SearchRepositoryIDs takes keyword and part of repository name to search, // it returns results in given range and number of total results. -func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { +func SearchRepositoryIDs(ctx context.Context, opts *SearchRepoOptions) ([]int64, int64, error) { opts.IncludeDescription = false cond := SearchRepositoryCondition(opts) - sess, count, err := searchRepositoryByCondition(db.DefaultContext, opts, cond) + sess, count, err := searchRepositoryByCondition(ctx, opts, cond) if err != nil { return nil, 0, err } @@ -716,7 +716,7 @@ func FindUserCodeAccessibleOwnerRepoIDs(ctx context.Context, ownerID int64, user } // GetUserRepositories returns a list of repositories of given user. -func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) { +func GetUserRepositories(ctx context.Context, opts *SearchRepoOptions) (RepositoryList, int64, error) { if len(opts.OrderBy) == 0 { opts.OrderBy = "updated_unix DESC" } @@ -734,7 +734,7 @@ func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) cond = cond.And(builder.In("lower_name", opts.LowerNames)) } - sess := db.GetEngine(db.DefaultContext) + sess := db.GetEngine(ctx) count, err := sess.Where(cond).Count(new(Repository)) if err != nil { diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go index 7097b6ea14..8a1799aac0 100644 --- a/models/repo/repo_list_test.go +++ b/models/repo/repo_list_test.go @@ -66,7 +66,7 @@ func getTestCases() []struct { count: 0, }, { - name: "PublicRepositoriesOfUser3", + name: "PublicRepositoriesOfOrg3", opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, count: 2, }, @@ -81,7 +81,7 @@ func getTestCases() []struct { count: 0, }, { - name: "PublicAndPrivateRepositoriesOfUser3", + name: "PublicAndPrivateRepositoriesOfOrg3", opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, count: 4, }, @@ -96,7 +96,7 @@ func getTestCases() []struct { count: 1, }, { - name: "PublicRepositoriesOfUser3IncludingCollaborative", + name: "PublicRepositoriesOfOrg3IncludingCollaborative", opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20}, count: 3, }, @@ -111,7 +111,7 @@ func getTestCases() []struct { count: 4, }, { - name: "PublicAndPrivateRepositoriesOfUser3IncludingCollaborative", + name: "PublicAndPrivateRepositoriesOfOrg3IncludingCollaborative", opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true}, count: 7, }, @@ -168,7 +168,7 @@ func getTestCases() []struct { { name: "OwnerSlashRepoSearch", opts: &repo_model.SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, - count: 3, + count: 2, }, { name: "OwnerSlashSearch", diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index 92a58ea3f9..4bbfeb634a 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -83,7 +83,7 @@ func TestMetas(t *testing.T) { repo.Units = nil - metas := repo.ComposeMetas() + metas := repo.ComposeMetas(db.DefaultContext) assert.Equal(t, "testRepo", metas["repo"]) assert.Equal(t, "testOwner", metas["user"]) @@ -97,7 +97,7 @@ func TestMetas(t *testing.T) { testSuccess := func(expectedStyle string) { repo.Units = []*repo_model.RepoUnit{&externalTracker} repo.RenderingMetas = nil - metas := repo.ComposeMetas() + metas := repo.ComposeMetas(db.DefaultContext) assert.Equal(t, expectedStyle, metas["style"]) assert.Equal(t, "testRepo", metas["repo"]) assert.Equal(t, "testOwner", metas["user"]) @@ -118,10 +118,10 @@ func TestMetas(t *testing.T) { repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 3) assert.NoError(t, err) - metas = repo.ComposeMetas() + metas = repo.ComposeMetas(db.DefaultContext) assert.Contains(t, metas, "org") assert.Contains(t, metas, "teams") - assert.Equal(t, "user3", metas["org"]) + assert.Equal(t, "org3", metas["org"]) assert.Equal(t, ",owners,team1,", metas["teams"]) } diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index b8804c6df1..89f28b8fca 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -279,14 +279,14 @@ func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err } // UpdateRepoUnit updates the provided repo unit -func UpdateRepoUnit(unit *RepoUnit) error { - _, err := db.GetEngine(db.DefaultContext).ID(unit.ID).Update(unit) +func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error { + _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) return err } // UpdateRepositoryUnits updates a repository's units -func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateRepositoryUnits(ctx context.Context, repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/repo/star.go b/models/repo/star.go index 89bdb7ac05..60737149da 100644 --- a/models/repo/star.go +++ b/models/repo/star.go @@ -24,8 +24,8 @@ func init() { } // StarRepo or unstar repository. -func StarRepo(userID, repoID int64, star bool) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func StarRepo(ctx context.Context, userID, repoID int64, star bool) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -72,8 +72,8 @@ func IsStaring(ctx context.Context, userID, repoID int64) bool { } // GetStargazers returns the users that starred the repo. -func GetStargazers(repo *Repository, opts db.ListOptions) ([]*user_model.User, error) { - sess := db.GetEngine(db.DefaultContext).Where("star.repo_id = ?", repo.ID). +func GetStargazers(ctx context.Context, repo *Repository, opts db.ListOptions) ([]*user_model.User, error) { + sess := db.GetEngine(ctx).Where("star.repo_id = ?", repo.ID). Join("LEFT", "star", "`user`.id = star.uid") if opts.Page > 0 { sess = db.SetSessionPagination(sess, &opts) diff --git a/models/repo/star_test.go b/models/repo/star_test.go index f15ac12ebe..62eac4e29a 100644 --- a/models/repo/star_test.go +++ b/models/repo/star_test.go @@ -18,11 +18,11 @@ func TestStarRepo(t *testing.T) { const userID = 2 const repoID = 1 unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) - assert.NoError(t, repo_model.StarRepo(userID, repoID, true)) + assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, true)) unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) - assert.NoError(t, repo_model.StarRepo(userID, repoID, true)) + assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, true)) unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) - assert.NoError(t, repo_model.StarRepo(userID, repoID, false)) + assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, false)) unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) } @@ -36,7 +36,7 @@ func TestRepository_GetStargazers(t *testing.T) { // repo with stargazers assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0}) + gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) assert.NoError(t, err) if assert.Len(t, gazers, 1) { assert.Equal(t, int64(2), gazers[0].ID) @@ -47,7 +47,7 @@ func TestRepository_GetStargazers2(t *testing.T) { // repo with stargazers assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0}) + gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) assert.NoError(t, err) assert.Len(t, gazers, 0) } @@ -57,15 +57,15 @@ func TestClearRepoStars(t *testing.T) { const userID = 2 const repoID = 1 unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) - assert.NoError(t, repo_model.StarRepo(userID, repoID, true)) + assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, true)) unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) - assert.NoError(t, repo_model.StarRepo(userID, repoID, false)) + assert.NoError(t, repo_model.StarRepo(db.DefaultContext, userID, repoID, false)) unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) assert.NoError(t, repo_model.ClearRepoStars(db.DefaultContext, repoID)) unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0}) + gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) assert.NoError(t, err) assert.Len(t, gazers, 0) } diff --git a/models/repo/topic.go b/models/repo/topic.go index 71302388b9..ca533fc1e0 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -92,9 +92,9 @@ func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []st } // GetTopicByName retrieves topic by name -func GetTopicByName(name string) (*Topic, error) { +func GetTopicByName(ctx context.Context, name string) (*Topic, error) { var topic Topic - if has, err := db.GetEngine(db.DefaultContext).Where("name = ?", name).Get(&topic); err != nil { + if has, err := db.GetEngine(ctx).Where("name = ?", name).Get(&topic); err != nil { return nil, err } else if !has { return nil, ErrTopicNotExist{name} @@ -192,8 +192,8 @@ func (opts *FindTopicOptions) toConds() builder.Cond { } // FindTopics retrieves the topics via FindTopicOptions -func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { - sess := db.GetEngine(db.DefaultContext).Select("topic.*").Where(opts.toConds()) +func FindTopics(ctx context.Context, opts *FindTopicOptions) ([]*Topic, int64, error) { + sess := db.GetEngine(ctx).Select("topic.*").Where(opts.toConds()) orderBy := "topic.repo_count DESC" if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") @@ -208,8 +208,8 @@ func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { } // CountTopics counts the number of topics matching the FindTopicOptions -func CountTopics(opts *FindTopicOptions) (int64, error) { - sess := db.GetEngine(db.DefaultContext).Where(opts.toConds()) +func CountTopics(ctx context.Context, opts *FindTopicOptions) (int64, error) { + sess := db.GetEngine(ctx).Where(opts.toConds()) if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") } @@ -231,8 +231,8 @@ func GetRepoTopicByName(ctx context.Context, repoID int64, topicName string) (*T } // AddTopic adds a topic name to a repository (if it does not already have it) -func AddTopic(repoID int64, topicName string) (*Topic, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func AddTopic(ctx context.Context, repoID int64, topicName string) (*Topic, error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -261,8 +261,8 @@ func AddTopic(repoID int64, topicName string) (*Topic, error) { } // DeleteTopic removes a topic name from a repository (if it has it) -func DeleteTopic(repoID int64, topicName string) (*Topic, error) { - topic, err := GetRepoTopicByName(db.DefaultContext, repoID, topicName) +func DeleteTopic(ctx context.Context, repoID int64, topicName string) (*Topic, error) { + topic, err := GetRepoTopicByName(ctx, repoID, topicName) if err != nil { return nil, err } @@ -271,26 +271,26 @@ func DeleteTopic(repoID int64, topicName string) (*Topic, error) { return nil, nil } - err = removeTopicFromRepo(db.DefaultContext, repoID, topic) + err = removeTopicFromRepo(ctx, repoID, topic) if err != nil { return nil, err } - err = syncTopicsInRepository(db.GetEngine(db.DefaultContext), repoID) + err = syncTopicsInRepository(db.GetEngine(ctx), repoID) return topic, err } // SaveTopics save topics to a repository -func SaveTopics(repoID int64, topicNames ...string) error { - topics, _, err := FindTopics(&FindTopicOptions{ +func SaveTopics(ctx context.Context, repoID int64, topicNames ...string) error { + topics, _, err := FindTopics(ctx, &FindTopicOptions{ RepoID: repoID, }) if err != nil { return err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/repo/topic_test.go b/models/repo/topic_test.go index aaed91bdd3..2b609e6d66 100644 --- a/models/repo/topic_test.go +++ b/models/repo/topic_test.go @@ -19,47 +19,47 @@ func TestAddTopic(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - topics, _, err := repo_model.FindTopics(&repo_model.FindTopicOptions{}) + topics, _, err := repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, total, err := repo_model.FindTopics(&repo_model.FindTopicOptions{ + topics, total, err := repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ ListOptions: db.ListOptions{Page: 1, PageSize: 2}, }) assert.NoError(t, err) assert.Len(t, topics, 2) assert.EqualValues(t, 6, total) - topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ + topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ RepoID: 1, }) assert.NoError(t, err) assert.Len(t, topics, repo1NrOfTopics) - assert.NoError(t, repo_model.SaveTopics(2, "golang")) + assert.NoError(t, repo_model.SaveTopics(db.DefaultContext, 2, "golang")) repo2NrOfTopics := 1 - topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{}) + topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ + topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) assert.Len(t, topics, repo2NrOfTopics) - assert.NoError(t, repo_model.SaveTopics(2, "golang", "gitea")) + assert.NoError(t, repo_model.SaveTopics(db.DefaultContext, 2, "golang", "gitea")) repo2NrOfTopics = 2 totalNrOfTopics++ - topic, err := repo_model.GetTopicByName("gitea") + topic, err := repo_model.GetTopicByName(db.DefaultContext, "gitea") assert.NoError(t, err) assert.EqualValues(t, 1, topic.RepoCount) - topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{}) + topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ + topics, _, err = repo_model.FindTopics(db.DefaultContext, &repo_model.FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) diff --git a/models/repo/update.go b/models/repo/update.go index c4fba32ad2..6ddf1a8905 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -16,11 +16,11 @@ import ( ) // UpdateRepositoryOwnerNames updates repository owner_names (this should only be used when the ownerName has changed case) -func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error { +func UpdateRepositoryOwnerNames(ctx context.Context, ownerID int64, ownerName string) error { if ownerID == 0 { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -36,8 +36,8 @@ func UpdateRepositoryOwnerNames(ownerID int64, ownerName string) error { } // UpdateRepositoryUpdatedTime updates a repository's updated time -func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { - _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) +func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime time.Time) error { + _, err := db.GetEngine(ctx).Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) return err } @@ -107,7 +107,7 @@ func (err ErrRepoFilesAlreadyExist) Unwrap() error { } // CheckCreateRepository check if could created a repository -func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdopt bool) error { +func CheckCreateRepository(ctx context.Context, doer, u *user_model.User, name string, overwriteOrAdopt bool) error { if !doer.CanCreateRepo() { return ErrReachLimitOfRepo{u.MaxRepoCreation} } @@ -116,7 +116,7 @@ func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdo return err } - has, err := IsRepositoryModelOrDirExist(db.DefaultContext, u, name) + has, err := IsRepositoryModelOrDirExist(ctx, u, name) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { @@ -136,18 +136,18 @@ func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdo } // ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName string) (err error) { +func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *Repository, newRepoName string) (err error) { oldRepoName := repo.Name newRepoName = strings.ToLower(newRepoName) if err = IsUsableRepoName(newRepoName); err != nil { return err } - if err := repo.LoadOwner(db.DefaultContext); err != nil { + if err := repo.LoadOwner(ctx); err != nil { return err } - has, err := IsRepositoryModelOrDirExist(db.DefaultContext, repo.Owner, newRepoName) + has, err := IsRepositoryModelOrDirExist(ctx, repo.Owner, newRepoName) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { @@ -171,7 +171,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s } } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/repo/upload.go b/models/repo/upload.go index 3b1b97c6d6..d96ab21bcd 100644 --- a/models/repo/upload.go +++ b/models/repo/upload.go @@ -5,6 +5,7 @@ package repo import ( + "context" "fmt" "io" "mime/multipart" @@ -61,7 +62,7 @@ func (upload *Upload) LocalPath() string { } // NewUpload creates a new upload object. -func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) { +func NewUpload(ctx context.Context, name string, buf []byte, file multipart.File) (_ *Upload, err error) { upload := &Upload{ UUID: gouuid.New().String(), Name: name, @@ -84,7 +85,7 @@ func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err err return nil, fmt.Errorf("Copy: %w", err) } - if _, err := db.GetEngine(db.DefaultContext).Insert(upload); err != nil { + if _, err := db.GetEngine(ctx).Insert(upload); err != nil { return nil, err } @@ -92,9 +93,9 @@ func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err err } // GetUploadByUUID returns the Upload by UUID -func GetUploadByUUID(uuid string) (*Upload, error) { +func GetUploadByUUID(ctx context.Context, uuid string) (*Upload, error) { upload := &Upload{} - has, err := db.GetEngine(db.DefaultContext).Where("uuid=?", uuid).Get(upload) + has, err := db.GetEngine(ctx).Where("uuid=?", uuid).Get(upload) if err != nil { return nil, err } else if !has { @@ -104,23 +105,23 @@ func GetUploadByUUID(uuid string) (*Upload, error) { } // GetUploadsByUUIDs returns multiple uploads by UUIDS -func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) { +func GetUploadsByUUIDs(ctx context.Context, uuids []string) ([]*Upload, error) { if len(uuids) == 0 { return []*Upload{}, nil } // Silently drop invalid uuids. uploads := make([]*Upload, 0, len(uuids)) - return uploads, db.GetEngine(db.DefaultContext).In("uuid", uuids).Find(&uploads) + return uploads, db.GetEngine(ctx).In("uuid", uuids).Find(&uploads) } // DeleteUploads deletes multiple uploads -func DeleteUploads(uploads ...*Upload) (err error) { +func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) { if len(uploads) == 0 { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -159,8 +160,8 @@ func DeleteUploads(uploads ...*Upload) (err error) { } // DeleteUploadByUUID deletes a upload by UUID -func DeleteUploadByUUID(uuid string) error { - upload, err := GetUploadByUUID(uuid) +func DeleteUploadByUUID(ctx context.Context, uuid string) error { + upload, err := GetUploadByUUID(ctx, uuid) if err != nil { if IsErrUploadNotExist(err) { return nil @@ -168,7 +169,7 @@ func DeleteUploadByUUID(uuid string) error { return fmt.Errorf("GetUploadByUUID: %w", err) } - if err := DeleteUploads(upload); err != nil { + if err := DeleteUploads(ctx, upload); err != nil { return fmt.Errorf("DeleteUpload: %w", err) } diff --git a/models/repo/watch.go b/models/repo/watch.go index 00f313ca7c..fba66d6dcb 100644 --- a/models/repo/watch.go +++ b/models/repo/watch.go @@ -59,8 +59,8 @@ func IsWatchMode(mode WatchMode) bool { } // IsWatching checks if user has watched given repository. -func IsWatching(userID, repoID int64) bool { - watch, err := GetWatch(db.DefaultContext, userID, repoID) +func IsWatching(ctx context.Context, userID, repoID int64) bool { + watch, err := GetWatch(ctx, userID, repoID) return err == nil && IsWatchMode(watch.Mode) } @@ -107,12 +107,12 @@ func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) } // WatchRepoMode watch repository in specific mode. -func WatchRepoMode(userID, repoID int64, mode WatchMode) (err error) { +func WatchRepoMode(ctx context.Context, userID, repoID int64, mode WatchMode) (err error) { var watch Watch - if watch, err = GetWatch(db.DefaultContext, userID, repoID); err != nil { + if watch, err = GetWatch(ctx, userID, repoID); err != nil { return err } - return watchRepoMode(db.DefaultContext, watch, mode) + return watchRepoMode(ctx, watch, mode) } // WatchRepo watch or unwatch repository. @@ -155,8 +155,8 @@ func GetRepoWatchersIDs(ctx context.Context, repoID int64) ([]int64, error) { } // GetRepoWatchers returns range of users watching given repository. -func GetRepoWatchers(repoID int64, opts db.ListOptions) ([]*user_model.User, error) { - sess := db.GetEngine(db.DefaultContext).Where("watch.repo_id=?", repoID). +func GetRepoWatchers(ctx context.Context, repoID int64, opts db.ListOptions) ([]*user_model.User, error) { + sess := db.GetEngine(ctx).Where("watch.repo_id=?", repoID). Join("LEFT", "watch", "`user`.id=`watch`.user_id"). And("`watch`.mode<>?", WatchModeDont) if opts.Page > 0 { diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go index 8b8c6d6250..7aa899291c 100644 --- a/models/repo/watch_test.go +++ b/models/repo/watch_test.go @@ -17,13 +17,13 @@ import ( func TestIsWatching(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.True(t, repo_model.IsWatching(1, 1)) - assert.True(t, repo_model.IsWatching(4, 1)) - assert.True(t, repo_model.IsWatching(11, 1)) + assert.True(t, repo_model.IsWatching(db.DefaultContext, 1, 1)) + assert.True(t, repo_model.IsWatching(db.DefaultContext, 4, 1)) + assert.True(t, repo_model.IsWatching(db.DefaultContext, 11, 1)) - assert.False(t, repo_model.IsWatching(1, 5)) - assert.False(t, repo_model.IsWatching(8, 1)) - assert.False(t, repo_model.IsWatching(unittest.NonexistentID, unittest.NonexistentID)) + assert.False(t, repo_model.IsWatching(db.DefaultContext, 1, 5)) + assert.False(t, repo_model.IsWatching(db.DefaultContext, 8, 1)) + assert.False(t, repo_model.IsWatching(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)) } func TestGetWatchers(t *testing.T) { @@ -47,7 +47,7 @@ func TestRepository_GetWatchers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err := repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) for _, watcher := range watchers { @@ -55,7 +55,7 @@ func TestRepository_GetWatchers(t *testing.T) { } repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, 0) } @@ -64,7 +64,7 @@ func TestWatchIfAuto(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err := repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) @@ -74,13 +74,13 @@ func TestWatchIfAuto(t *testing.T) { // Must not add watch assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 8, 1, true)) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should not add watch assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 10, 1, true)) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) @@ -88,31 +88,31 @@ func TestWatchIfAuto(t *testing.T) { // Must not add watch assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 8, 1, true)) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should not add watch assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, false)) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should add watch assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, true)) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount+1) // Should remove watch, inhibit from adding auto assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, 12, 1, false)) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Must not add watch assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, true)) - watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) } @@ -122,18 +122,18 @@ func TestWatchRepoMode(t *testing.T) { unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeAuto)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeAuto)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeAuto}, 1) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNormal)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeNormal)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeNormal}, 1) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeDont)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeDont)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeDont}, 1) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNone)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeNone)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) } diff --git a/models/repo_test.go b/models/repo_test.go index da66b16136..2a8a4a743e 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -20,5 +20,5 @@ func TestCheckRepoStats(t *testing.T) { func TestDoctorUserStarNum(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, DoctorUserStarNum()) + assert.NoError(t, DoctorUserStarNum(db.DefaultContext)) } diff --git a/models/repo_transfer.go b/models/repo_transfer.go index 1c873cec57..630c243c8e 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -79,8 +79,8 @@ func (r *RepoTransfer) LoadAttributes(ctx context.Context) error { // CanUserAcceptTransfer checks if the user has the rights to accept/decline a repo transfer. // For user, it checks if it's himself // For organizations, it checks if the user is able to create repos -func (r *RepoTransfer) CanUserAcceptTransfer(u *user_model.User) bool { - if err := r.LoadAttributes(db.DefaultContext); err != nil { +func (r *RepoTransfer) CanUserAcceptTransfer(ctx context.Context, u *user_model.User) bool { + if err := r.LoadAttributes(ctx); err != nil { log.Error("LoadAttributes: %v", err) return false } @@ -89,7 +89,7 @@ func (r *RepoTransfer) CanUserAcceptTransfer(u *user_model.User) bool { return r.RecipientID == u.ID } - allowed, err := organization.CanCreateOrgRepo(db.DefaultContext, r.RecipientID, u.ID) + allowed, err := organization.CanCreateOrgRepo(ctx, r.RecipientID, u.ID) if err != nil { log.Error("CanCreateOrgRepo: %v", err) return false @@ -122,8 +122,8 @@ func deleteRepositoryTransfer(ctx context.Context, repoID int64) error { // CancelRepositoryTransfer marks the repository as ready and remove pending transfer entry, // thus cancel the transfer process. -func CancelRepositoryTransfer(repo *repo_model.Repository) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CancelRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -199,7 +199,7 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m } // TransferOwnership transfers all corresponding repository items from old user to new one. -func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_model.Repository) (err error) { +func TransferOwnership(ctx context.Context, doer *user_model.User, newOwnerName string, repo *repo_model.Repository) (err error) { repoRenamed := false wikiRenamed := false oldOwnerName := doer.Name @@ -234,7 +234,7 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo } }() - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/repo_transfer_test.go b/models/repo_transfer_test.go index 8352adc948..b55cef9473 100644 --- a/models/repo_transfer_test.go +++ b/models/repo_transfer_test.go @@ -25,7 +25,7 @@ func TestRepositoryTransfer(t *testing.T) { assert.NotNil(t, transfer) // Cancel transfer - assert.NoError(t, CancelRepositoryTransfer(repo)) + assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo)) transfer, err = GetPendingRepositoryTransfer(db.DefaultContext, repo) assert.Error(t, err) @@ -41,10 +41,10 @@ func TestRepositoryTransfer(t *testing.T) { assert.NoError(t, transfer.LoadAttributes(db.DefaultContext)) assert.Equal(t, "user2", transfer.Recipient.Name) - user6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Only transfer can be started at any given time - err = CreatePendingRepositoryTransfer(db.DefaultContext, doer, user6, repo.ID, nil) + err = CreatePendingRepositoryTransfer(db.DefaultContext, doer, org6, repo.ID, nil) assert.Error(t, err) assert.True(t, IsErrRepoTransferInProgress(err)) @@ -53,5 +53,5 @@ func TestRepositoryTransfer(t *testing.T) { assert.Error(t, err) // Cancel transfer - assert.NoError(t, CancelRepositoryTransfer(repo)) + assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo)) } diff --git a/models/system/appstate.go b/models/system/appstate.go index 7378537678..01faa1a5be 100644 --- a/models/system/appstate.go +++ b/models/system/appstate.go @@ -23,8 +23,8 @@ func init() { } // SaveAppStateContent saves the app state item to database -func SaveAppStateContent(key, content string) error { - return db.WithTx(db.DefaultContext, func(ctx context.Context) error { +func SaveAppStateContent(ctx context.Context, key, content string) error { + return db.WithTx(ctx, func(ctx context.Context) error { eng := db.GetEngine(ctx) // try to update existing row res, err := eng.Exec("UPDATE app_state SET revision=revision+1, content=? WHERE id=?", content, key) @@ -43,8 +43,8 @@ func SaveAppStateContent(key, content string) error { } // GetAppStateContent gets an app state from database -func GetAppStateContent(key string) (content string, err error) { - e := db.GetEngine(db.DefaultContext) +func GetAppStateContent(ctx context.Context, key string) (content string, err error) { + e := db.GetEngine(ctx) appState := &AppState{ID: key} has, err := e.Get(appState) if err != nil { diff --git a/models/system/main_test.go b/models/system/main_test.go index e074abc155..6bc27a7cff 100644 --- a/models/system/main_test.go +++ b/models/system/main_test.go @@ -4,7 +4,6 @@ package system_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -16,7 +15,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/system/notice.go b/models/system/notice.go index 784ad74375..058b78e677 100644 --- a/models/system/notice.go +++ b/models/system/notice.go @@ -88,34 +88,34 @@ func RemoveStorageWithNotice(ctx context.Context, bucket storage.ObjectStorage, } // CountNotices returns number of notices. -func CountNotices() int64 { - count, _ := db.GetEngine(db.DefaultContext).Count(new(Notice)) +func CountNotices(ctx context.Context) int64 { + count, _ := db.GetEngine(ctx).Count(new(Notice)) return count } // Notices returns notices in given page. -func Notices(page, pageSize int) ([]*Notice, error) { +func Notices(ctx context.Context, page, pageSize int) ([]*Notice, error) { notices := make([]*Notice, 0, pageSize) - return notices, db.GetEngine(db.DefaultContext). + return notices, db.GetEngine(ctx). Limit(pageSize, (page-1)*pageSize). Desc("created_unix"). Find(¬ices) } // DeleteNotice deletes a system notice by given ID. -func DeleteNotice(id int64) error { - _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Notice)) +func DeleteNotice(ctx context.Context, id int64) error { + _, err := db.GetEngine(ctx).ID(id).Delete(new(Notice)) return err } // DeleteNotices deletes all notices with ID from start to end (inclusive). -func DeleteNotices(start, end int64) error { +func DeleteNotices(ctx context.Context, start, end int64) error { if start == 0 && end == 0 { - _, err := db.GetEngine(db.DefaultContext).Exec("DELETE FROM notice") + _, err := db.GetEngine(ctx).Exec("DELETE FROM notice") return err } - sess := db.GetEngine(db.DefaultContext).Where("id >= ?", start) + sess := db.GetEngine(ctx).Where("id >= ?", start) if end > 0 { sess.And("id <= ?", end) } @@ -124,22 +124,22 @@ func DeleteNotices(start, end int64) error { } // DeleteNoticesByIDs deletes notices by given IDs. -func DeleteNoticesByIDs(ids []int64) error { +func DeleteNoticesByIDs(ctx context.Context, ids []int64) error { if len(ids) == 0 { return nil } - _, err := db.GetEngine(db.DefaultContext). + _, err := db.GetEngine(ctx). In("id", ids). Delete(new(Notice)) return err } // DeleteOldSystemNotices deletes all old system notices from database. -func DeleteOldSystemNotices(olderThan time.Duration) (err error) { +func DeleteOldSystemNotices(ctx context.Context, olderThan time.Duration) (err error) { if olderThan <= 0 { return nil } - _, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Notice{}) + _, err = db.GetEngine(ctx).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Notice{}) return err } diff --git a/models/system/notice_test.go b/models/system/notice_test.go index 01eb9b57a5..e8ce05d332 100644 --- a/models/system/notice_test.go +++ b/models/system/notice_test.go @@ -49,20 +49,20 @@ func TestCreateRepositoryNotice(t *testing.T) { func TestCountNotices(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.Equal(t, int64(3), system.CountNotices()) + assert.Equal(t, int64(3), system.CountNotices(db.DefaultContext)) } func TestNotices(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - notices, err := system.Notices(1, 2) + notices, err := system.Notices(db.DefaultContext, 1, 2) assert.NoError(t, err) if assert.Len(t, notices, 2) { assert.Equal(t, int64(3), notices[0].ID) assert.Equal(t, int64(2), notices[1].ID) } - notices, err = system.Notices(2, 2) + notices, err = system.Notices(db.DefaultContext, 2, 2) assert.NoError(t, err) if assert.Len(t, notices, 1) { assert.Equal(t, int64(1), notices[0].ID) @@ -73,7 +73,7 @@ func TestDeleteNotice(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNotice(3)) + assert.NoError(t, system.DeleteNotice(db.DefaultContext, 3)) unittest.AssertNotExistsBean(t, &system.Notice{ID: 3}) } @@ -84,7 +84,7 @@ func TestDeleteNotices(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNotices(1, 2)) + assert.NoError(t, system.DeleteNotices(db.DefaultContext, 1, 2)) unittest.AssertNotExistsBean(t, &system.Notice{ID: 1}) unittest.AssertNotExistsBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) @@ -97,7 +97,7 @@ func TestDeleteNotices2(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNotices(3, 2)) + assert.NoError(t, system.DeleteNotices(db.DefaultContext, 3, 2)) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) @@ -109,7 +109,7 @@ func TestDeleteNoticesByIDs(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNoticesByIDs([]int64{1, 3})) + assert.NoError(t, system.DeleteNoticesByIDs(db.DefaultContext, []int64{1, 3})) unittest.AssertNotExistsBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertNotExistsBean(t, &system.Notice{ID: 3}) diff --git a/models/system/setting.go b/models/system/setting.go index 9fffa74e94..1ae6f5c652 100644 --- a/models/system/setting.go +++ b/models/system/setting.go @@ -5,26 +5,21 @@ package system import ( "context" - "fmt" - "net/url" - "strconv" - "strings" + "math" + "sync" + "time" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/cache" - setting_module "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/timeutil" - - "strk.kbt.io/projects/go/libravatar" - "xorm.io/builder" ) -// Setting is a key value store of user settings type Setting struct { ID int64 `xorm:"pk autoincr"` - SettingKey string `xorm:"varchar(255) unique"` // ensure key is always lowercase + SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase SettingValue string `xorm:"text"` - Version int `xorm:"version"` // prevent to override + Version int `xorm:"version"` Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` } @@ -34,281 +29,119 @@ func (s *Setting) TableName() string { return "system_setting" } -func (s *Setting) GetValueBool() bool { - if s == nil { - return false - } - - b, _ := strconv.ParseBool(s.SettingValue) - return b -} - func init() { db.RegisterModel(new(Setting)) } -// ErrSettingIsNotExist represents an error that a setting is not exist with special key -type ErrSettingIsNotExist struct { - Key string -} +const keyRevision = "revision" -// Error implements error -func (err ErrSettingIsNotExist) Error() string { - return fmt.Sprintf("System setting[%s] is not exist", err.Key) -} - -// IsErrSettingIsNotExist return true if err is ErrSettingIsNotExist -func IsErrSettingIsNotExist(err error) bool { - _, ok := err.(ErrSettingIsNotExist) - return ok -} - -// ErrDataExpired represents an error that update a record which has been updated by another thread -type ErrDataExpired struct { - Key string -} - -// Error implements error -func (err ErrDataExpired) Error() string { - return fmt.Sprintf("System setting[%s] has been updated by another thread", err.Key) -} - -// IsErrDataExpired return true if err is ErrDataExpired -func IsErrDataExpired(err error) bool { - _, ok := err.(ErrDataExpired) - return ok -} - -// GetSetting returns specific setting without using the cache -func GetSetting(ctx context.Context, key string) (*Setting, error) { - v, err := GetSettings(ctx, []string{key}) - if err != nil { - return nil, err - } - if len(v) == 0 { - return nil, ErrSettingIsNotExist{key} - } - return v[strings.ToLower(key)], nil -} - -const contextCacheKey = "system_setting" - -// GetSettingWithCache returns the setting value via the key -func GetSettingWithCache(ctx context.Context, key, defaultVal string) (string, error) { - return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { - return cache.GetString(genSettingCacheKey(key), func() (string, error) { - res, err := GetSetting(ctx, key) - if err != nil { - if IsErrSettingIsNotExist(err) { - return defaultVal, nil - } - return "", err - } - return res.SettingValue, nil - }) - }) -} - -// GetSettingBool return bool value of setting, -// none existing keys and errors are ignored and result in false -func GetSettingBool(ctx context.Context, key string, defaultVal bool) (bool, error) { - s, err := GetSetting(ctx, key) - switch { - case err == nil: - v, _ := strconv.ParseBool(s.SettingValue) - return v, nil - case IsErrSettingIsNotExist(err): - return defaultVal, nil - default: - return false, err - } -} - -func GetSettingWithCacheBool(ctx context.Context, key string, defaultVal bool) bool { - s, _ := GetSettingWithCache(ctx, key, strconv.FormatBool(defaultVal)) - v, _ := strconv.ParseBool(s) - return v -} - -// GetSettings returns specific settings -func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error) { - for i := 0; i < len(keys); i++ { - keys[i] = strings.ToLower(keys[i]) - } - settings := make([]*Setting, 0, len(keys)) - if err := db.GetEngine(ctx). - Where(builder.In("setting_key", keys)). - Find(&settings); err != nil { - return nil, err - } - settingsMap := make(map[string]*Setting) - for _, s := range settings { - settingsMap[s.SettingKey] = s - } - return settingsMap, nil -} - -type AllSettings map[string]*Setting - -func (settings AllSettings) Get(key string) Setting { - if v, ok := settings[strings.ToLower(key)]; ok { - return *v - } - return Setting{} -} - -func (settings AllSettings) GetBool(key string) bool { - b, _ := strconv.ParseBool(settings.Get(key).SettingValue) - return b -} - -func (settings AllSettings) GetVersion(key string) int { - return settings.Get(key).Version -} - -// GetAllSettings returns all settings from user -func GetAllSettings(ctx context.Context) (AllSettings, error) { - settings := make([]*Setting, 0, 5) - if err := db.GetEngine(ctx). - Find(&settings); err != nil { - return nil, err - } - settingsMap := make(map[string]*Setting) - for _, s := range settings { - settingsMap[s.SettingKey] = s - } - return settingsMap, nil -} - -// DeleteSetting deletes a specific setting for a user -func DeleteSetting(ctx context.Context, setting *Setting) error { - cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey) - cache.Remove(genSettingCacheKey(setting.SettingKey)) - _, err := db.GetEngine(ctx).Delete(setting) - return err -} - -func SetSettingNoVersion(ctx context.Context, key, value string) error { - s, err := GetSetting(ctx, key) - if IsErrSettingIsNotExist(err) { - return SetSetting(ctx, &Setting{ - SettingKey: key, - SettingValue: value, - }) - } - if err != nil { - return err - } - s.SettingValue = value - return SetSetting(ctx, s) -} - -// SetSetting updates a users' setting for a specific key -func SetSetting(ctx context.Context, setting *Setting) error { - if err := upsertSettingValue(ctx, strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil { - return err - } - - setting.Version++ - - cc := cache.GetCache() - if cc != nil { - if err := cc.Put(genSettingCacheKey(setting.SettingKey), setting.SettingValue, setting_module.CacheService.TTLSeconds()); err != nil { - return err +func GetRevision(ctx context.Context) int { + revision := &Setting{SettingKey: keyRevision} + if has, err := db.GetByBean(ctx, revision); err != nil { + return 0 + } else if !has { + err = db.Insert(ctx, &Setting{SettingKey: keyRevision, Version: 1}) + if err != nil { + return 0 } + return 1 + } else if revision.Version <= 0 || revision.Version >= math.MaxInt-1 { + _, err = db.Exec(ctx, "UPDATE system_setting SET version=1 WHERE setting_key=?", keyRevision) + if err != nil { + return 0 + } + return 1 } - cache.SetContextData(ctx, contextCacheKey, setting.SettingKey, setting.SettingValue) - return nil + return revision.Version } -func upsertSettingValue(parentCtx context.Context, key, value string, version int) error { - return db.WithTx(parentCtx, func(ctx context.Context) error { +func GetAllSettings(ctx context.Context) (revision int, res map[string]string, err error) { + _ = GetRevision(ctx) // prepare the "revision" key ahead + var settings []*Setting + if err := db.GetEngine(ctx). + Find(&settings); err != nil { + return 0, nil, err + } + res = make(map[string]string) + for _, s := range settings { + if s.SettingKey == keyRevision { + revision = s.Version + } + res[s.SettingKey] = s.SettingValue + } + return revision, res, nil +} + +func SetSettings(ctx context.Context, settings map[string]string) error { + _ = GetRevision(ctx) // prepare the "revision" key ahead + return db.WithTx(ctx, func(ctx context.Context) error { e := db.GetEngine(ctx) - - // here we use a general method to do a safe upsert for different databases (and most transaction levels) - // 1. try to UPDATE the record and acquire the transaction write lock - // if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly - // if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist (b) value is not changed - // 2. do a SELECT to check if the row exists or not (we already have the transaction lock) - // 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe) - // - // to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1` - // to make sure the UPDATE always returns a non-zero value for existing (unchanged) records. - - res, err := e.Exec("UPDATE system_setting SET setting_value=?, version = version+1 WHERE setting_key=? AND version=?", value, key, version) + _, err := db.Exec(ctx, "UPDATE system_setting SET version=version+1 WHERE setting_key=?", keyRevision) if err != nil { return err } - rows, _ := res.RowsAffected() - if rows > 0 { - // the existing row is updated, so we can return - return nil + for k, v := range settings { + res, err := e.Exec("UPDATE system_setting SET setting_value=? WHERE setting_key=?", v, k) + if err != nil { + return err + } + rows, _ := res.RowsAffected() + if rows == 0 { // if no existing row, insert a new row + if _, err = e.Insert(&Setting{SettingKey: k, SettingValue: v}); err != nil { + return err + } + } } - - // in case the value isn't changed, update would return 0 rows changed, so we need this check - has, err := e.Exist(&Setting{SettingKey: key}) - if err != nil { - return err - } - if has { - return ErrDataExpired{Key: key} - } - - // if no existing row, insert a new row - _, err = e.Insert(&Setting{SettingKey: key, SettingValue: value}) - return err + return nil }) } -var ( - GravatarSourceURL *url.URL - LibravatarService *libravatar.Libravatar -) +type dbConfigCachedGetter struct { + mu sync.RWMutex -func Init(ctx context.Context) error { - disableGravatar, err := GetSettingBool(ctx, KeyPictureDisableGravatar, setting_module.GetDefaultDisableGravatar()) - if err != nil { - return err - } - - enableFederatedAvatar, err := GetSettingBool(ctx, KeyPictureEnableFederatedAvatar, setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)) - if err != nil { - return err - } - - if setting_module.OfflineMode { - if !disableGravatar { - if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil { - return fmt.Errorf("failed to set setting %q: %w", KeyPictureDisableGravatar, err) - } - } - disableGravatar = true - - if enableFederatedAvatar { - if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil { - return fmt.Errorf("failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err) - } - } - enableFederatedAvatar = false - } - - if enableFederatedAvatar || !disableGravatar { - var err error - GravatarSourceURL, err = url.Parse(setting_module.GravatarSource) - if err != nil { - return fmt.Errorf("failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err) - } - } - - if GravatarSourceURL != nil && enableFederatedAvatar { - LibravatarService = libravatar.New() - if GravatarSourceURL.Scheme == "https" { - LibravatarService.SetUseHTTPS(true) - LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host) - } else { - LibravatarService.SetUseHTTPS(false) - LibravatarService.SetFallbackHost(GravatarSourceURL.Host) - } - } - return nil + cacheTime time.Time + revision int + settings map[string]string +} + +var _ config.DynKeyGetter = (*dbConfigCachedGetter)(nil) + +func (d *dbConfigCachedGetter) GetValue(ctx context.Context, key string) (v string, has bool) { + d.mu.RLock() + defer d.mu.RUnlock() + v, has = d.settings[key] + return v, has +} + +func (d *dbConfigCachedGetter) GetRevision(ctx context.Context) int { + d.mu.RLock() + defer d.mu.RUnlock() + if time.Since(d.cacheTime) < time.Second { + return d.revision + } + if GetRevision(ctx) != d.revision { + d.mu.RUnlock() + d.mu.Lock() + rev, set, err := GetAllSettings(ctx) + if err != nil { + log.Error("Unable to get all settings: %v", err) + } else { + d.cacheTime = time.Now() + d.revision = rev + d.settings = set + } + d.mu.Unlock() + d.mu.RLock() + } + return d.revision +} + +func (d *dbConfigCachedGetter) InvalidateCache() { + d.mu.Lock() + d.cacheTime = time.Time{} + d.mu.Unlock() +} + +func NewDatabaseDynKeyGetter() config.DynKeyGetter { + return &dbConfigCachedGetter{} } diff --git a/models/system/setting_key.go b/models/system/setting_key.go deleted file mode 100644 index ad083ed1ea..0000000000 --- a/models/system/setting_key.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package system - -// enumerate all system setting keys -const ( - KeyPictureDisableGravatar = "picture.disable_gravatar" - KeyPictureEnableFederatedAvatar = "picture.enable_federated_avatar" -) - -// genSettingCacheKey returns the cache key for some configuration -func genSettingCacheKey(key string) string { - return "system.setting." + key -} diff --git a/models/system/setting_test.go b/models/system/setting_test.go index e6997ad783..4117420e5f 100644 --- a/models/system/setting_test.go +++ b/models/system/setting_test.go @@ -4,7 +4,6 @@ package system_test import ( - "strings" "testing" "code.gitea.io/gitea/models/db" @@ -15,43 +14,29 @@ import ( ) func TestSettings(t *testing.T) { - keyName := "server.LFS_LOCKS_PAGING_NUM" + keyName := "test.key" assert.NoError(t, unittest.PrepareTestDatabase()) - newSetting := &system.Setting{SettingKey: keyName, SettingValue: "50"} + assert.NoError(t, db.TruncateBeans(db.DefaultContext, &system.Setting{})) - // create setting - err := system.SetSetting(db.DefaultContext, newSetting) - assert.NoError(t, err) - // test about saving unchanged values - err = system.SetSetting(db.DefaultContext, newSetting) + rev, settings, err := system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) + assert.EqualValues(t, 1, rev) + assert.Len(t, settings, 1) // there is only one "revision" key - // get specific setting - settings, err := system.GetSettings(db.DefaultContext, []string{keyName}) + err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "true"}) assert.NoError(t, err) - assert.Len(t, settings, 1) - assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue) - - // updated setting - updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: settings[strings.ToLower(keyName)].Version} - err = system.SetSetting(db.DefaultContext, updatedSetting) - assert.NoError(t, err) - - value, err := system.GetSetting(db.DefaultContext, keyName) - assert.NoError(t, err) - assert.EqualValues(t, updatedSetting.SettingValue, value.SettingValue) - - // get all settings - settings, err = system.GetAllSettings(db.DefaultContext) - assert.NoError(t, err) - assert.Len(t, settings, 3) - assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue) - - // delete setting - err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)}) - assert.NoError(t, err) - settings, err = system.GetAllSettings(db.DefaultContext) + rev, settings, err = system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) + assert.EqualValues(t, 2, rev) assert.Len(t, settings, 2) + assert.EqualValues(t, "true", settings[keyName]) + + err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "false"}) + assert.NoError(t, err) + rev, settings, err = system.GetAllSettings(db.DefaultContext) + assert.NoError(t, err) + assert.EqualValues(t, 3, rev) + assert.Len(t, settings, 2) + assert.EqualValues(t, "false", settings[keyName]) } diff --git a/models/unit/unit.go b/models/unit/unit.go index ca3e7f999d..b216712d37 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -91,6 +91,7 @@ var ( TypeWiki, TypeProjects, TypePackages, + TypeActions, } // ForkRepoUnits contains the default unit types for forks diff --git a/models/unit/unit_test.go b/models/unit/unit_test.go index 5c50a106bd..d80d8b118d 100644 --- a/models/unit/unit_test.go +++ b/models/unit/unit_test.go @@ -89,7 +89,7 @@ func TestLoadUnitConfig(t *testing.T) { setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"} assert.NoError(t, LoadUnitConfig()) assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits) - assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects}, DefaultRepoUnits) + assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects, TypeActions}, DefaultRepoUnits) assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) }) } diff --git a/models/unittest/consistency.go b/models/unittest/consistency.go index faa02589aa..71839001be 100644 --- a/models/unittest/consistency.go +++ b/models/unittest/consistency.go @@ -47,8 +47,7 @@ func checkForConsistency(t assert.TestingT, bean any) { assert.NoError(t, err) f := consistencyCheckMap[tb.Name] if f == nil { - assert.Fail(t, "unknown bean type: %#v", bean) - return + assert.FailNow(t, "unknown bean type: %#v", bean) } f(t, bean) } diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 1ff0fdc25b..4c668ad04b 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -13,11 +13,12 @@ import ( "testing" "code.gitea.io/gitea/models/db" - system_model "code.gitea.io/gitea/models/system" + "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" @@ -62,35 +63,46 @@ func InitSettings(extraConfigs ...string) { // TestOptions represents test options type TestOptions struct { - GiteaRootPath string - FixtureFiles []string - SetUp func() error // SetUp will be executed before all tests in this package - TearDown func() error // TearDown will be executed after all tests in this package + FixtureFiles []string + SetUp func() error // SetUp will be executed before all tests in this package + TearDown func() error // TearDown will be executed after all tests in this package } // MainTest a reusable TestMain(..) function for unit tests that need to use a // test database. Creates the test database, and sets necessary settings. -func MainTest(m *testing.M, testOpts *TestOptions) { - setting.CustomPath = filepath.Join(testOpts.GiteaRootPath, "custom") +func MainTest(m *testing.M, testOpts ...*TestOptions) { + searchDir, _ := os.Getwd() + for searchDir != "" { + if _, err := os.Stat(filepath.Join(searchDir, "go.mod")); err == nil { + break // The "go.mod" should be the one for Gitea repository + } + if dir := filepath.Dir(searchDir); dir == searchDir { + searchDir = "" // reaches the root of filesystem + } else { + searchDir = dir + } + } + if searchDir == "" { + panic("The tests should run in a Gitea repository, there should be a 'go.mod' in the root") + } + + giteaRoot = searchDir + setting.CustomPath = filepath.Join(giteaRoot, "custom") InitSettings() - var err error - - giteaRoot = testOpts.GiteaRootPath - fixturesDir = filepath.Join(testOpts.GiteaRootPath, "models", "fixtures") - + fixturesDir = filepath.Join(giteaRoot, "models", "fixtures") var opts FixturesOptions - if len(testOpts.FixtureFiles) == 0 { + if len(testOpts) == 0 || len(testOpts[0].FixtureFiles) == 0 { opts.Dir = fixturesDir } else { - for _, f := range testOpts.FixtureFiles { + for _, f := range testOpts[0].FixtureFiles { if len(f) != 0 { opts.Files = append(opts.Files, filepath.Join(fixturesDir, f)) } } } - if err = CreateTestEngine(opts); err != nil { + if err := CreateTestEngine(opts); err != nil { fatalTestError("Error creating test engine: %v\n", err) } @@ -112,8 +124,8 @@ func MainTest(m *testing.M, testOpts *TestOptions) { fatalTestError("TempDir: %v\n", err) } setting.AppDataPath = appDataPath - setting.AppWorkPath = testOpts.GiteaRootPath - setting.StaticRootPath = testOpts.GiteaRootPath + setting.AppWorkPath = giteaRoot + setting.StaticRootPath = giteaRoot setting.GravatarSource = "https://secure.gravatar.com/avatar/" setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments") @@ -134,17 +146,15 @@ func MainTest(m *testing.M, testOpts *TestOptions) { setting.IncomingEmail.ReplyToAddress = "incoming+%{token}@localhost" + config.SetDynGetter(system.NewDatabaseDynKeyGetter()) + if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } - if err = system_model.Init(db.DefaultContext); err != nil { - fatalTestError("models.Init: %v\n", err) - } - if err = util.RemoveAll(repoRootPath); err != nil { fatalTestError("util.RemoveAll: %v\n", err) } - if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil { + if err = CopyDir(filepath.Join(giteaRoot, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil { fatalTestError("util.CopyDir: %v\n", err) } @@ -171,16 +181,16 @@ func MainTest(m *testing.M, testOpts *TestOptions) { } } - if testOpts.SetUp != nil { - if err := testOpts.SetUp(); err != nil { + if len(testOpts) > 0 && testOpts[0].SetUp != nil { + if err := testOpts[0].SetUp(); err != nil { fatalTestError("set up failed: %v\n", err) } } exitStatus := m.Run() - if testOpts.TearDown != nil { - if err := testOpts.TearDown(); err != nil { + if len(testOpts) > 0 && testOpts[0].TearDown != nil { + if err := testOpts[0].TearDown(); err != nil { fatalTestError("tear down failed: %v\n", err) } } diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go index 399040580c..d47bceea1e 100644 --- a/models/unittest/unit_tests.go +++ b/models/unittest/unit_tests.go @@ -96,7 +96,15 @@ func AssertExistsAndLoadMap(t assert.TestingT, table string, conditions ...any) // GetCount get the count of a bean func GetCount(t assert.TestingT, bean any, conditions ...any) int { e := db.GetEngine(db.DefaultContext) - count, err := whereOrderConditions(e, conditions).Count(bean) + for _, condition := range conditions { + switch cond := condition.(type) { + case *testCond: + e = e.Where(cond.query, cond.args...) + default: + e = e.Where(cond) + } + } + count, err := e.Count(bean) assert.NoError(t, err) return int(count) } diff --git a/models/user/avatar.go b/models/user/avatar.go index 7f996fa79a..c6937d7b51 100644 --- a/models/user/avatar.go +++ b/models/user/avatar.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/models/db" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -59,17 +58,14 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error { // AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string { - if u.ID == -1 { - // ghost user + if u.IsGhost() { return avatars.DefaultAvatarLink() } useLocalAvatar := false autoGenerateAvatar := false - disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar, - setting.GetDefaultDisableGravatar(), - ) + disableGravatar := setting.Config().Picture.DisableGravatar.Value(ctx) switch { case u.UseCustomAvatar: diff --git a/models/user/email_address.go b/models/user/email_address.go index e916249e30..f1ed6692cf 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -178,9 +178,9 @@ func ValidateEmail(email string) error { } // GetEmailAddresses returns all email addresses belongs to given user. -func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { +func GetEmailAddresses(ctx context.Context, uid int64) ([]*EmailAddress, error) { emails := make([]*EmailAddress, 0, 5) - if err := db.GetEngine(db.DefaultContext). + if err := db.GetEngine(ctx). Where("uid=?", uid). Asc("id"). Find(&emails); err != nil { @@ -190,10 +190,10 @@ func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { } // GetEmailAddressByID gets a user's email address by ID -func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) { +func GetEmailAddressByID(ctx context.Context, uid, id int64) (*EmailAddress, error) { // User ID is required for security reasons email := &EmailAddress{UID: uid} - if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil { + if has, err := db.GetEngine(ctx).ID(id).Get(email); err != nil { return nil, err } else if !has { return nil, nil @@ -253,7 +253,7 @@ func AddEmailAddress(ctx context.Context, email *EmailAddress) error { } // AddEmailAddresses adds an email address to given user. -func AddEmailAddresses(emails []*EmailAddress) error { +func AddEmailAddresses(ctx context.Context, emails []*EmailAddress) error { if len(emails) == 0 { return nil } @@ -261,7 +261,7 @@ func AddEmailAddresses(emails []*EmailAddress) error { // Check if any of them has been used for i := range emails { emails[i].Email = strings.TrimSpace(emails[i].Email) - used, err := IsEmailUsed(db.DefaultContext, emails[i].Email) + used, err := IsEmailUsed(ctx, emails[i].Email) if err != nil { return err } else if used { @@ -272,7 +272,7 @@ func AddEmailAddresses(emails []*EmailAddress) error { } } - if err := db.Insert(db.DefaultContext, emails); err != nil { + if err := db.Insert(ctx, emails); err != nil { return fmt.Errorf("Insert: %w", err) } @@ -280,7 +280,7 @@ func AddEmailAddresses(emails []*EmailAddress) error { } // DeleteEmailAddress deletes an email address of given user. -func DeleteEmailAddress(email *EmailAddress) (err error) { +func DeleteEmailAddress(ctx context.Context, email *EmailAddress) (err error) { if email.IsPrimary { return ErrPrimaryEmailCannotDelete{Email: email.Email} } @@ -291,12 +291,12 @@ func DeleteEmailAddress(email *EmailAddress) (err error) { UID: email.UID, } if email.ID > 0 { - deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address) + deleted, err = db.GetEngine(ctx).ID(email.ID).Delete(&address) } else { if email.Email != "" && email.LowerEmail == "" { email.LowerEmail = strings.ToLower(email.Email) } - deleted, err = db.GetEngine(db.DefaultContext). + deleted, err = db.GetEngine(ctx). Where("lower_email=?", email.LowerEmail). Delete(&address) } @@ -310,9 +310,9 @@ func DeleteEmailAddress(email *EmailAddress) (err error) { } // DeleteEmailAddresses deletes multiple email addresses -func DeleteEmailAddresses(emails []*EmailAddress) (err error) { +func DeleteEmailAddresses(ctx context.Context, emails []*EmailAddress) (err error) { for i := range emails { - if err = DeleteEmailAddress(emails[i]); err != nil { + if err = DeleteEmailAddress(ctx, emails[i]); err != nil { return err } } @@ -329,8 +329,8 @@ func DeleteInactiveEmailAddresses(ctx context.Context) error { } // ActivateEmail activates the email address to given user. -func ActivateEmail(email *EmailAddress) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ActivateEmail(ctx context.Context, email *EmailAddress) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -357,8 +357,8 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e } // MakeEmailPrimary sets primary email address of given user. -func MakeEmailPrimary(email *EmailAddress) error { - has, err := db.GetEngine(db.DefaultContext).Get(email) +func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error { + has, err := db.GetEngine(ctx).Get(email) if err != nil { return err } else if !has { @@ -370,7 +370,7 @@ func MakeEmailPrimary(email *EmailAddress) error { } user := &User{} - has, err = db.GetEngine(db.DefaultContext).ID(email.UID).Get(user) + has, err = db.GetEngine(ctx).ID(email.UID).Get(user) if err != nil { return err } else if !has { @@ -381,7 +381,7 @@ func MakeEmailPrimary(email *EmailAddress) error { } } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -411,17 +411,17 @@ func MakeEmailPrimary(email *EmailAddress) error { } // VerifyActiveEmailCode verifies active email code when active account -func VerifyActiveEmailCode(code, email string) *EmailAddress { +func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress { minutes := setting.Service.ActiveCodeLives - if user := GetVerifyUser(code); user != nil { + 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) if base.VerifyTimeLimitCode(data, minutes, prefix) { emailAddress := &EmailAddress{UID: user.ID, Email: email} - if has, _ := db.GetEngine(db.DefaultContext).Get(emailAddress); has { + if has, _ := db.GetEngine(ctx).Get(emailAddress); has { return emailAddress } } @@ -466,7 +466,7 @@ type SearchEmailResult struct { // SearchEmails takes options i.e. keyword and part of email name to search, // it returns results in given range and number of total results. -func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) { +func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) { var cond builder.Cond = builder.Eq{"`user`.`type`": UserTypeIndividual} if len(opts.Keyword) > 0 { likeStr := "%" + strings.ToLower(opts.Keyword) + "%" @@ -491,7 +491,7 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) cond = cond.And(builder.Eq{"email_address.is_activated": false}) } - count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid"). + count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.ID = email_address.uid"). Where(cond).Count(new(EmailAddress)) if err != nil { return nil, 0, fmt.Errorf("Count: %w", err) @@ -505,7 +505,7 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) opts.SetDefaultValues() emails := make([]*SearchEmailResult, 0, opts.PageSize) - err = db.GetEngine(db.DefaultContext).Table("email_address"). + err = db.GetEngine(ctx).Table("email_address"). Select("email_address.*, `user`.name, `user`.full_name"). Join("INNER", "`user`", "`user`.ID = email_address.uid"). Where(cond). @@ -518,8 +518,8 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) // ActivateUserEmail will change the activated state of an email address, // either primary or secondary (all in the email_address table) -func ActivateUserEmail(userID int64, email string, activate bool) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ActivateUserEmail(ctx context.Context, userID int64, email string, activate bool) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go index 53fd18c303..7f3ca75cfd 100644 --- a/models/user/email_address_test.go +++ b/models/user/email_address_test.go @@ -17,14 +17,14 @@ import ( func TestGetEmailAddresses(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - emails, _ := user_model.GetEmailAddresses(int64(1)) + emails, _ := user_model.GetEmailAddresses(db.DefaultContext, int64(1)) if assert.Len(t, emails, 3) { assert.True(t, emails[0].IsPrimary) assert.True(t, emails[2].IsActivated) assert.False(t, emails[2].IsPrimary) } - emails, _ = user_model.GetEmailAddresses(int64(2)) + emails, _ = user_model.GetEmailAddresses(db.DefaultContext, int64(2)) if assert.Len(t, emails, 2) { assert.True(t, emails[0].IsPrimary) assert.True(t, emails[0].IsActivated) @@ -76,10 +76,10 @@ func TestAddEmailAddresses(t *testing.T) { LowerEmail: "user5678@example.com", IsActivated: true, } - assert.NoError(t, user_model.AddEmailAddresses(emails)) + assert.NoError(t, user_model.AddEmailAddresses(db.DefaultContext, emails)) // ErrEmailAlreadyUsed - err := user_model.AddEmailAddresses(emails) + err := user_model.AddEmailAddresses(db.DefaultContext, emails) assert.Error(t, err) assert.True(t, user_model.IsErrEmailAlreadyUsed(err)) } @@ -87,21 +87,21 @@ func TestAddEmailAddresses(t *testing.T) { func TestDeleteEmailAddress(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, user_model.DeleteEmailAddress(&user_model.EmailAddress{ + assert.NoError(t, user_model.DeleteEmailAddress(db.DefaultContext, &user_model.EmailAddress{ UID: int64(1), ID: int64(33), Email: "user1-2@example.com", LowerEmail: "user1-2@example.com", })) - assert.NoError(t, user_model.DeleteEmailAddress(&user_model.EmailAddress{ + assert.NoError(t, user_model.DeleteEmailAddress(db.DefaultContext, &user_model.EmailAddress{ UID: int64(1), Email: "user1-3@example.com", LowerEmail: "user1-3@example.com", })) // Email address does not exist - err := user_model.DeleteEmailAddress(&user_model.EmailAddress{ + err := user_model.DeleteEmailAddress(db.DefaultContext, &user_model.EmailAddress{ UID: int64(1), Email: "user1234567890@example.com", LowerEmail: "user1234567890@example.com", @@ -125,10 +125,10 @@ func TestDeleteEmailAddresses(t *testing.T) { Email: "user2-2@example.com", LowerEmail: "user2-2@example.com", } - assert.NoError(t, user_model.DeleteEmailAddresses(emails)) + assert.NoError(t, user_model.DeleteEmailAddresses(db.DefaultContext, emails)) // ErrEmailAlreadyUsed - err := user_model.DeleteEmailAddresses(emails) + err := user_model.DeleteEmailAddresses(db.DefaultContext, emails) assert.Error(t, err) } @@ -138,28 +138,28 @@ func TestMakeEmailPrimary(t *testing.T) { email := &user_model.EmailAddress{ Email: "user567890@example.com", } - err := user_model.MakeEmailPrimary(email) + err := user_model.MakeEmailPrimary(db.DefaultContext, email) assert.Error(t, err) assert.EqualError(t, err, user_model.ErrEmailAddressNotExist{Email: email.Email}.Error()) email = &user_model.EmailAddress{ Email: "user11@example.com", } - err = user_model.MakeEmailPrimary(email) + err = user_model.MakeEmailPrimary(db.DefaultContext, email) assert.Error(t, err) assert.EqualError(t, err, user_model.ErrEmailNotActivated.Error()) email = &user_model.EmailAddress{ Email: "user9999999@example.com", } - err = user_model.MakeEmailPrimary(email) + err = user_model.MakeEmailPrimary(db.DefaultContext, email) assert.Error(t, err) assert.True(t, user_model.IsErrUserNotExist(err)) email = &user_model.EmailAddress{ Email: "user101@example.com", } - err = user_model.MakeEmailPrimary(email) + err = user_model.MakeEmailPrimary(db.DefaultContext, email) assert.NoError(t, err) user, _ := user_model.GetUserByID(db.DefaultContext, int64(10)) @@ -174,9 +174,9 @@ func TestActivate(t *testing.T) { UID: int64(1), Email: "user11@example.com", } - assert.NoError(t, user_model.ActivateEmail(email)) + assert.NoError(t, user_model.ActivateEmail(db.DefaultContext, email)) - emails, _ := user_model.GetEmailAddresses(int64(1)) + emails, _ := user_model.GetEmailAddresses(db.DefaultContext, int64(1)) assert.Len(t, emails, 3) assert.True(t, emails[0].IsActivated) assert.True(t, emails[0].IsPrimary) @@ -194,7 +194,7 @@ func TestListEmails(t *testing.T) { PageSize: 10000, }, } - emails, count, err := user_model.SearchEmails(opts) + emails, count, err := user_model.SearchEmails(db.DefaultContext, opts) assert.NoError(t, err) assert.NotEqual(t, int64(0), count) assert.True(t, count > 5) @@ -209,18 +209,18 @@ func TestListEmails(t *testing.T) { } assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 })) - // 'user3' is an organization + // 'org3' is an organization assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 3 })) // Must find no records opts = &user_model.SearchEmailOptions{Keyword: "NOTFOUND"} - emails, count, err = user_model.SearchEmails(opts) + emails, count, err = user_model.SearchEmails(db.DefaultContext, opts) assert.NoError(t, err) assert.Equal(t, int64(0), count) // Must find users 'user2', 'user28', etc. opts = &user_model.SearchEmailOptions{Keyword: "user2"} - emails, count, err = user_model.SearchEmails(opts) + emails, count, err = user_model.SearchEmails(db.DefaultContext, opts) assert.NoError(t, err) assert.NotEqual(t, int64(0), count) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 2 })) @@ -228,14 +228,14 @@ func TestListEmails(t *testing.T) { // Must find only primary addresses (i.e. from the `user` table) opts = &user_model.SearchEmailOptions{IsPrimary: util.OptionalBoolTrue} - emails, _, err = user_model.SearchEmails(opts) + emails, _, err = user_model.SearchEmails(db.DefaultContext, opts) assert.NoError(t, err) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary })) assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary })) // Must find only inactive addresses (i.e. not validated) opts = &user_model.SearchEmailOptions{IsActivated: util.OptionalBoolFalse} - emails, _, err = user_model.SearchEmails(opts) + emails, _, err = user_model.SearchEmails(db.DefaultContext, opts) assert.NoError(t, err) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated })) assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsActivated })) @@ -247,7 +247,7 @@ func TestListEmails(t *testing.T) { Page: 1, }, } - emails, count, err = user_model.SearchEmails(opts) + emails, count, err = user_model.SearchEmails(db.DefaultContext, opts) assert.NoError(t, err) assert.Len(t, emails, 5) assert.Greater(t, count, int64(len(emails))) diff --git a/models/user/external_login_user.go b/models/user/external_login_user.go index 3a56240435..4121c5d89f 100644 --- a/models/user/external_login_user.go +++ b/models/user/external_login_user.go @@ -92,14 +92,14 @@ func init() { } // GetExternalLogin checks if a externalID in loginSourceID scope already exists -func GetExternalLogin(externalLoginUser *ExternalLoginUser) (bool, error) { - return db.GetEngine(db.DefaultContext).Get(externalLoginUser) +func GetExternalLogin(ctx context.Context, externalLoginUser *ExternalLoginUser) (bool, error) { + return db.GetEngine(ctx).Get(externalLoginUser) } // ListAccountLinks returns a map with the ExternalLoginUser and its LoginSource -func ListAccountLinks(user *User) ([]*ExternalLoginUser, error) { +func ListAccountLinks(ctx context.Context, user *User) ([]*ExternalLoginUser, error) { externalAccounts := make([]*ExternalLoginUser, 0, 5) - err := db.GetEngine(db.DefaultContext).Where("user_id=?", user.ID). + err := db.GetEngine(ctx).Where("user_id=?", user.ID). Desc("login_source_id"). Find(&externalAccounts) if err != nil { @@ -110,8 +110,8 @@ func ListAccountLinks(user *User) ([]*ExternalLoginUser, error) { } // LinkExternalToUser link the external user to the user -func LinkExternalToUser(user *User, externalLoginUser *ExternalLoginUser) error { - has, err := db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", externalLoginUser.ExternalID, externalLoginUser.LoginSourceID). +func LinkExternalToUser(ctx context.Context, user *User, externalLoginUser *ExternalLoginUser) error { + has, err := db.GetEngine(ctx).Where("external_id=? AND login_source_id=?", externalLoginUser.ExternalID, externalLoginUser.LoginSourceID). NoAutoCondition(). Exist(externalLoginUser) if err != nil { @@ -120,13 +120,13 @@ func LinkExternalToUser(user *User, externalLoginUser *ExternalLoginUser) error return ErrExternalLoginUserAlreadyExist{externalLoginUser.ExternalID, user.ID, externalLoginUser.LoginSourceID} } - _, err = db.GetEngine(db.DefaultContext).Insert(externalLoginUser) + _, err = db.GetEngine(ctx).Insert(externalLoginUser) return err } // RemoveAccountLink will remove all external login sources for the given user -func RemoveAccountLink(user *User, loginSourceID int64) (int64, error) { - deleted, err := db.GetEngine(db.DefaultContext).Delete(&ExternalLoginUser{UserID: user.ID, LoginSourceID: loginSourceID}) +func RemoveAccountLink(ctx context.Context, user *User, loginSourceID int64) (int64, error) { + deleted, err := db.GetEngine(ctx).Delete(&ExternalLoginUser{UserID: user.ID, LoginSourceID: loginSourceID}) if err != nil { return deleted, err } @@ -143,9 +143,9 @@ func RemoveAllAccountLinks(ctx context.Context, user *User) error { } // GetUserIDByExternalUserID get user id according to provider and userID -func GetUserIDByExternalUserID(provider, userID string) (int64, error) { +func GetUserIDByExternalUserID(ctx context.Context, provider, userID string) (int64, error) { var id int64 - _, err := db.GetEngine(db.DefaultContext).Table("external_login_user"). + _, err := db.GetEngine(ctx).Table("external_login_user"). Select("user_id"). Where("provider=?", provider). And("external_id=?", userID). @@ -157,8 +157,8 @@ func GetUserIDByExternalUserID(provider, userID string) (int64, error) { } // UpdateExternalUserByExternalID updates an external user's information -func UpdateExternalUserByExternalID(external *ExternalLoginUser) error { - has, err := db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID). +func UpdateExternalUserByExternalID(ctx context.Context, external *ExternalLoginUser) error { + has, err := db.GetEngine(ctx).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID). NoAutoCondition(). Exist(external) if err != nil { @@ -167,7 +167,7 @@ func UpdateExternalUserByExternalID(external *ExternalLoginUser) error { return ErrExternalLoginUserNotExist{external.UserID, external.LoginSourceID} } - _, err = db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID).AllCols().Update(external) + _, err = db.GetEngine(ctx).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID).AllCols().Update(external) return err } @@ -187,9 +187,9 @@ func (opts FindExternalUserOptions) toConds() builder.Cond { } // FindExternalUsersByProvider represents external users via provider -func FindExternalUsersByProvider(opts FindExternalUserOptions) ([]ExternalLoginUser, error) { +func FindExternalUsersByProvider(ctx context.Context, opts FindExternalUserOptions) ([]ExternalLoginUser, error) { var users []ExternalLoginUser - err := db.GetEngine(db.DefaultContext).Where(opts.toConds()). + err := db.GetEngine(ctx).Where(opts.toConds()). Limit(opts.Limit, opts.Start). OrderBy("login_source_id ASC, external_id ASC"). Find(&users) diff --git a/models/user/follow.go b/models/user/follow.go index 7efecc26a7..f4dd2891ff 100644 --- a/models/user/follow.go +++ b/models/user/follow.go @@ -4,6 +4,8 @@ package user import ( + "context" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" ) @@ -21,18 +23,18 @@ func init() { } // IsFollowing returns true if user is following followID. -func IsFollowing(userID, followID int64) bool { - has, _ := db.GetEngine(db.DefaultContext).Get(&Follow{UserID: userID, FollowID: followID}) +func IsFollowing(ctx context.Context, userID, followID int64) bool { + has, _ := db.GetEngine(ctx).Get(&Follow{UserID: userID, FollowID: followID}) return has } // FollowUser marks someone be another's follower. -func FollowUser(userID, followID int64) (err error) { - if userID == followID || IsFollowing(userID, followID) { +func FollowUser(ctx context.Context, userID, followID int64) (err error) { + if userID == followID || IsFollowing(ctx, userID, followID) { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -53,12 +55,12 @@ func FollowUser(userID, followID int64) (err error) { } // UnfollowUser unmarks someone as another's follower. -func UnfollowUser(userID, followID int64) (err error) { - if userID == followID || !IsFollowing(userID, followID) { +func UnfollowUser(ctx context.Context, userID, followID int64) (err error) { + if userID == followID || !IsFollowing(ctx, userID, followID) { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/user/follow_test.go b/models/user/follow_test.go index fc408d5257..c327d935ae 100644 --- a/models/user/follow_test.go +++ b/models/user/follow_test.go @@ -6,6 +6,7 @@ package user_test import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -14,9 +15,9 @@ import ( func TestIsFollowing(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.True(t, user_model.IsFollowing(4, 2)) - assert.False(t, user_model.IsFollowing(2, 4)) - assert.False(t, user_model.IsFollowing(5, unittest.NonexistentID)) - assert.False(t, user_model.IsFollowing(unittest.NonexistentID, 5)) - assert.False(t, user_model.IsFollowing(unittest.NonexistentID, unittest.NonexistentID)) + assert.True(t, user_model.IsFollowing(db.DefaultContext, 4, 2)) + assert.False(t, user_model.IsFollowing(db.DefaultContext, 2, 4)) + assert.False(t, user_model.IsFollowing(db.DefaultContext, 5, unittest.NonexistentID)) + assert.False(t, user_model.IsFollowing(db.DefaultContext, unittest.NonexistentID, 5)) + assert.False(t, user_model.IsFollowing(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)) } diff --git a/models/user/list.go b/models/user/list.go index 6b3b7bea9a..ca589d1e02 100644 --- a/models/user/list.go +++ b/models/user/list.go @@ -25,19 +25,19 @@ func (users UserList) GetUserIDs() []int64 { } // GetTwoFaStatus return state of 2FA enrollement -func (users UserList) GetTwoFaStatus() map[int64]bool { +func (users UserList) GetTwoFaStatus(ctx context.Context) map[int64]bool { results := make(map[int64]bool, len(users)) for _, user := range users { results[user.ID] = false // Set default to false } - if tokenMaps, err := users.loadTwoFactorStatus(db.DefaultContext); err == nil { + if tokenMaps, err := users.loadTwoFactorStatus(ctx); err == nil { for _, token := range tokenMaps { results[token.UID] = true } } - if ids, err := users.userIDsWithWebAuthn(db.DefaultContext); err == nil { + if ids, err := users.userIDsWithWebAuthn(ctx); err == nil { for _, id := range ids { results[id] = true } @@ -71,12 +71,12 @@ func (users UserList) userIDsWithWebAuthn(ctx context.Context) ([]int64, error) } // GetUsersByIDs returns all resolved users from a list of Ids. -func GetUsersByIDs(ids []int64) (UserList, error) { +func GetUsersByIDs(ctx context.Context, ids []int64) (UserList, error) { ous := make([]*User, 0, len(ids)) if len(ids) == 0 { return ous, nil } - err := db.GetEngine(db.DefaultContext).In("id", ids). + err := db.GetEngine(ctx).In("id", ids). Asc("name"). Find(&ous) return ous, err diff --git a/models/user/main_test.go b/models/user/main_test.go index 8833cc7386..a626d323a7 100644 --- a/models/user/main_test.go +++ b/models/user/main_test.go @@ -4,7 +4,6 @@ package user_test import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -16,7 +15,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/models/user/openid.go b/models/user/openid.go index 596ff182bc..ee4ecabae0 100644 --- a/models/user/openid.go +++ b/models/user/openid.go @@ -28,9 +28,9 @@ func init() { } // GetUserOpenIDs returns all openid addresses that belongs to given user. -func GetUserOpenIDs(uid int64) ([]*UserOpenID, error) { +func GetUserOpenIDs(ctx context.Context, uid int64) ([]*UserOpenID, error) { openids := make([]*UserOpenID, 0, 5) - if err := db.GetEngine(db.DefaultContext). + if err := db.GetEngine(ctx). Where("uid=?", uid). Asc("id"). Find(&openids); err != nil { @@ -82,16 +82,16 @@ func AddUserOpenID(ctx context.Context, openid *UserOpenID) error { } // DeleteUserOpenID deletes an openid address of given user. -func DeleteUserOpenID(openid *UserOpenID) (err error) { +func DeleteUserOpenID(ctx context.Context, openid *UserOpenID) (err error) { var deleted int64 // ask to check UID address := UserOpenID{ UID: openid.UID, } if openid.ID > 0 { - deleted, err = db.GetEngine(db.DefaultContext).ID(openid.ID).Delete(&address) + deleted, err = db.GetEngine(ctx).ID(openid.ID).Delete(&address) } else { - deleted, err = db.GetEngine(db.DefaultContext). + deleted, err = db.GetEngine(ctx). Where("openid=?", openid.URI). Delete(&address) } @@ -105,7 +105,7 @@ func DeleteUserOpenID(openid *UserOpenID) (err error) { } // ToggleUserOpenIDVisibility toggles visibility of an openid address of given user. -func ToggleUserOpenIDVisibility(id int64) (err error) { - _, err = db.GetEngine(db.DefaultContext).Exec("update `user_open_id` set `show` = not `show` where `id` = ?", id) +func ToggleUserOpenIDVisibility(ctx context.Context, id int64) (err error) { + _, err = db.GetEngine(ctx).Exec("update `user_open_id` set `show` = not `show` where `id` = ?", id) return err } diff --git a/models/user/openid_test.go b/models/user/openid_test.go index 6f0eae55e7..27e6edd1e0 100644 --- a/models/user/openid_test.go +++ b/models/user/openid_test.go @@ -6,6 +6,7 @@ package user_test import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -15,7 +16,7 @@ import ( func TestGetUserOpenIDs(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - oids, err := user_model.GetUserOpenIDs(int64(1)) + oids, err := user_model.GetUserOpenIDs(db.DefaultContext, int64(1)) if assert.NoError(t, err) && assert.Len(t, oids, 2) { assert.Equal(t, "https://user1.domain1.tld/", oids[0].URI) assert.False(t, oids[0].Show) @@ -23,7 +24,7 @@ func TestGetUserOpenIDs(t *testing.T) { assert.True(t, oids[1].Show) } - oids, err = user_model.GetUserOpenIDs(int64(2)) + oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2)) if assert.NoError(t, err) && assert.Len(t, oids, 1) { assert.Equal(t, "https://domain1.tld/user2/", oids[0].URI) assert.True(t, oids[0].Show) @@ -32,28 +33,28 @@ func TestGetUserOpenIDs(t *testing.T) { func TestToggleUserOpenIDVisibility(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - oids, err := user_model.GetUserOpenIDs(int64(2)) + oids, err := user_model.GetUserOpenIDs(db.DefaultContext, int64(2)) if !assert.NoError(t, err) || !assert.Len(t, oids, 1) { return } assert.True(t, oids[0].Show) - err = user_model.ToggleUserOpenIDVisibility(oids[0].ID) + err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID) if !assert.NoError(t, err) { return } - oids, err = user_model.GetUserOpenIDs(int64(2)) + oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2)) if !assert.NoError(t, err) || !assert.Len(t, oids, 1) { return } assert.False(t, oids[0].Show) - err = user_model.ToggleUserOpenIDVisibility(oids[0].ID) + err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID) if !assert.NoError(t, err) { return } - oids, err = user_model.GetUserOpenIDs(int64(2)) + oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2)) if !assert.NoError(t, err) { return } diff --git a/models/user/redirect.go b/models/user/redirect.go index 42e991888a..5a40d4df3b 100644 --- a/models/user/redirect.go +++ b/models/user/redirect.go @@ -48,10 +48,10 @@ func init() { } // LookupUserRedirect look up userID if a user has a redirect name -func LookupUserRedirect(userName string) (int64, error) { +func LookupUserRedirect(ctx context.Context, userName string) (int64, error) { userName = strings.ToLower(userName) redirect := &Redirect{LowerName: userName} - if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil { + if has, err := db.GetEngine(ctx).Get(redirect); err != nil { return 0, err } else if !has { return 0, ErrUserRedirectNotExist{Name: userName} diff --git a/models/user/redirect_test.go b/models/user/redirect_test.go index 3d2ea3aeec..484c5a663f 100644 --- a/models/user/redirect_test.go +++ b/models/user/redirect_test.go @@ -6,6 +6,7 @@ package user_test import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -15,10 +16,10 @@ import ( func TestLookupUserRedirect(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - userID, err := user_model.LookupUserRedirect("olduser1") + userID, err := user_model.LookupUserRedirect(db.DefaultContext, "olduser1") assert.NoError(t, err) assert.EqualValues(t, 1, userID) - _, err = user_model.LookupUserRedirect("doesnotexist") + _, err = user_model.LookupUserRedirect(db.DefaultContext, "doesnotexist") assert.True(t, user_model.IsErrUserRedirectNotExist(err)) } diff --git a/models/user/search.go b/models/user/search.go index bf35fcd9eb..0fa278c257 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -4,6 +4,7 @@ package user import ( + "context" "fmt" "strings" @@ -34,12 +35,26 @@ type SearchUserOptions struct { IsRestricted util.OptionalBool IsTwoFactorEnabled util.OptionalBool IsProhibitLogin util.OptionalBool + IncludeReserved bool ExtraParamStrings map[string]string } -func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session { - var cond builder.Cond = builder.Eq{"type": opts.Type} +func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session { + var cond builder.Cond + cond = builder.Eq{"type": opts.Type} + if opts.IncludeReserved { + if opts.Type == UserTypeIndividual { + cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or( + builder.Eq{"type": UserTypeBot}, + ).Or( + builder.Eq{"type": UserTypeRemoteUser}, + ) + } else if opts.Type == UserTypeOrganization { + cond = cond.Or(builder.Eq{"type": UserTypeOrganizationReserved}) + } + } + if len(opts.Keyword) > 0 { lowerKeyword := strings.ToLower(opts.Keyword) keywordCond := builder.Or( @@ -87,7 +102,7 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session { cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.IsTrue()}) } - e := db.GetEngine(db.DefaultContext) + e := db.GetEngine(ctx) if opts.IsTwoFactorEnabled.IsNone() { return e.Where(cond) } @@ -108,8 +123,8 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session { // SearchUsers takes options i.e. keyword and part of user name to search, // it returns results in given range and number of total results. -func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) { - sessCount := opts.toSearchQueryBase() +func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _ int64, _ error) { + sessCount := opts.toSearchQueryBase(ctx) defer sessCount.Close() count, err := sessCount.Count(new(User)) if err != nil { @@ -120,7 +135,7 @@ func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) { opts.OrderBy = db.SearchOrderByAlphabetically } - sessQuery := opts.toSearchQueryBase().OrderBy(opts.OrderBy.String()) + sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String()) defer sessQuery.Close() if opts.Page != 0 { sessQuery = db.SetSessionPagination(sessQuery, opts) diff --git a/models/user/setting.go b/models/user/setting.go index a41e494db9..b4af0e5ccd 100644 --- a/models/user/setting.go +++ b/models/user/setting.go @@ -59,9 +59,9 @@ func genSettingCacheKey(userID int64, key string) string { } // GetSetting returns the setting value via the key -func GetSetting(uid int64, key string) (string, error) { +func GetSetting(ctx context.Context, uid int64, key string) (string, error) { return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) { - res, err := GetSettingNoCache(uid, key) + res, err := GetSettingNoCache(ctx, uid, key) if err != nil { return "", err } @@ -70,8 +70,8 @@ func GetSetting(uid int64, key string) (string, error) { } // GetSettingNoCache returns specific setting without using the cache -func GetSettingNoCache(uid int64, key string) (*Setting, error) { - v, err := GetSettings(uid, []string{key}) +func GetSettingNoCache(ctx context.Context, uid int64, key string) (*Setting, error) { + v, err := GetSettings(ctx, uid, []string{key}) if err != nil { return nil, err } @@ -82,9 +82,9 @@ func GetSettingNoCache(uid int64, key string) (*Setting, error) { } // GetSettings returns specific settings from user -func GetSettings(uid int64, keys []string) (map[string]*Setting, error) { +func GetSettings(ctx context.Context, uid int64, keys []string) (map[string]*Setting, error) { settings := make([]*Setting, 0, len(keys)) - if err := db.GetEngine(db.DefaultContext). + if err := db.GetEngine(ctx). Where("user_id=?", uid). And(builder.In("setting_key", keys)). Find(&settings); err != nil { @@ -98,9 +98,9 @@ func GetSettings(uid int64, keys []string) (map[string]*Setting, error) { } // GetUserAllSettings returns all settings from user -func GetUserAllSettings(uid int64) (map[string]*Setting, error) { +func GetUserAllSettings(ctx context.Context, uid int64) (map[string]*Setting, error) { settings := make([]*Setting, 0, 5) - if err := db.GetEngine(db.DefaultContext). + if err := db.GetEngine(ctx). Where("user_id=?", uid). Find(&settings); err != nil { return nil, err @@ -123,13 +123,13 @@ func validateUserSettingKey(key string) error { } // GetUserSetting gets a specific setting for a user -func GetUserSetting(userID int64, key string, def ...string) (string, error) { +func GetUserSetting(ctx context.Context, userID int64, key string, def ...string) (string, error) { if err := validateUserSettingKey(key); err != nil { return "", err } setting := &Setting{UserID: userID, SettingKey: key} - has, err := db.GetEngine(db.DefaultContext).Get(setting) + has, err := db.GetEngine(ctx).Get(setting) if err != nil { return "", err } @@ -143,24 +143,24 @@ func GetUserSetting(userID int64, key string, def ...string) (string, error) { } // DeleteUserSetting deletes a specific setting for a user -func DeleteUserSetting(userID int64, key string) error { +func DeleteUserSetting(ctx context.Context, userID int64, key string) error { if err := validateUserSettingKey(key); err != nil { return err } cache.Remove(genSettingCacheKey(userID, key)) - _, err := db.GetEngine(db.DefaultContext).Delete(&Setting{UserID: userID, SettingKey: key}) + _, err := db.GetEngine(ctx).Delete(&Setting{UserID: userID, SettingKey: key}) return err } // SetUserSetting updates a users' setting for a specific key -func SetUserSetting(userID int64, key, value string) error { +func SetUserSetting(ctx context.Context, userID int64, key, value string) error { if err := validateUserSettingKey(key); err != nil { return err } - if err := upsertUserSettingValue(userID, key, value); err != nil { + if err := upsertUserSettingValue(ctx, userID, key, value); err != nil { return err } @@ -172,8 +172,8 @@ func SetUserSetting(userID int64, key, value string) error { return nil } -func upsertUserSettingValue(userID int64, key, value string) error { - return db.WithTx(db.DefaultContext, func(ctx context.Context) error { +func upsertUserSettingValue(ctx context.Context, userID int64, key, value string) error { + return db.WithTx(ctx, func(ctx context.Context) error { e := db.GetEngine(ctx) // here we use a general method to do a safe upsert for different databases (and most transaction levels) diff --git a/models/user/setting_test.go b/models/user/setting_test.go index d0d612d25d..c56fe93075 100644 --- a/models/user/setting_test.go +++ b/models/user/setting_test.go @@ -6,6 +6,7 @@ package user_test import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -19,41 +20,41 @@ func TestSettings(t *testing.T) { newSetting := &user_model.Setting{UserID: 99, SettingKey: keyName, SettingValue: "Gitea User Setting Test"} // create setting - err := user_model.SetUserSetting(newSetting.UserID, newSetting.SettingKey, newSetting.SettingValue) + err := user_model.SetUserSetting(db.DefaultContext, newSetting.UserID, newSetting.SettingKey, newSetting.SettingValue) assert.NoError(t, err) // test about saving unchanged values - err = user_model.SetUserSetting(newSetting.UserID, newSetting.SettingKey, newSetting.SettingValue) + err = user_model.SetUserSetting(db.DefaultContext, newSetting.UserID, newSetting.SettingKey, newSetting.SettingValue) assert.NoError(t, err) // get specific setting - settings, err := user_model.GetSettings(99, []string{keyName}) + settings, err := user_model.GetSettings(db.DefaultContext, 99, []string{keyName}) assert.NoError(t, err) assert.Len(t, settings, 1) assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue) - settingValue, err := user_model.GetUserSetting(99, keyName) + settingValue, err := user_model.GetUserSetting(db.DefaultContext, 99, keyName) assert.NoError(t, err) assert.EqualValues(t, newSetting.SettingValue, settingValue) - settingValue, err = user_model.GetUserSetting(99, "no_such") + settingValue, err = user_model.GetUserSetting(db.DefaultContext, 99, "no_such") assert.NoError(t, err) assert.EqualValues(t, "", settingValue) // updated setting updatedSetting := &user_model.Setting{UserID: 99, SettingKey: keyName, SettingValue: "Updated"} - err = user_model.SetUserSetting(updatedSetting.UserID, updatedSetting.SettingKey, updatedSetting.SettingValue) + err = user_model.SetUserSetting(db.DefaultContext, updatedSetting.UserID, updatedSetting.SettingKey, updatedSetting.SettingValue) assert.NoError(t, err) // get all settings - settings, err = user_model.GetUserAllSettings(99) + settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99) assert.NoError(t, err) assert.Len(t, settings, 1) assert.EqualValues(t, updatedSetting.SettingValue, settings[updatedSetting.SettingKey].SettingValue) // delete setting - err = user_model.DeleteUserSetting(99, keyName) + err = user_model.DeleteUserSetting(db.DefaultContext, 99, keyName) assert.NoError(t, err) - settings, err = user_model.GetUserAllSettings(99) + settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99) assert.NoError(t, err) assert.Len(t, settings, 0) } diff --git a/models/user/user.go b/models/user/user.go index 86cf2ad280..ce0e055b15 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -42,7 +42,7 @@ const ( // UserTypeOrganization defines an organization UserTypeOrganization - // UserTypeReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on + // UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on UserTypeUserReserved // UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved @@ -192,15 +192,15 @@ func (u *User) SetLastLogin() { } // UpdateUserDiffViewStyle updates the users diff view style -func UpdateUserDiffViewStyle(u *User, style string) error { +func UpdateUserDiffViewStyle(ctx context.Context, u *User, style string) error { u.DiffViewStyle = style - return UpdateUserCols(db.DefaultContext, u, "diff_view_style") + return UpdateUserCols(ctx, u, "diff_view_style") } // UpdateUserTheme updates a users' theme irrespective of the site wide theme -func UpdateUserTheme(u *User, themeName string) error { +func UpdateUserTheme(ctx context.Context, u *User, themeName string) error { u.Theme = themeName - return UpdateUserCols(db.DefaultContext, u, "theme") + return UpdateUserCols(ctx, u, "theme") } // GetPlaceholderEmail returns an noreply email @@ -218,9 +218,9 @@ func (u *User) GetEmail() string { } // GetAllUsers returns a slice of all individual users found in DB. -func GetAllUsers() ([]*User, error) { +func GetAllUsers(ctx context.Context) ([]*User, error) { users := make([]*User, 0) - return users, db.GetEngine(db.DefaultContext).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) + return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) } // IsLocal returns true if user login type is LoginPlain. @@ -478,9 +478,9 @@ func (u *User) EmailNotifications() string { } // SetEmailNotifications sets the user's email notification preference -func SetEmailNotifications(u *User, set string) error { +func SetEmailNotifications(ctx context.Context, u *User, set string) error { u.EmailNotificationsPreference = set - if err := UpdateUserCols(db.DefaultContext, u, "email_notifications_preference"); err != nil { + if err := UpdateUserCols(ctx, u, "email_notifications_preference"); err != nil { log.Error("SetEmailNotifications: %v", err) return err } @@ -582,7 +582,7 @@ type CreateUserOverwriteOptions struct { } // CreateUser creates record of a new user. -func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { +func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { if err = IsUsableUsername(u.Name); err != nil { return err } @@ -640,7 +640,7 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e return err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -711,8 +711,8 @@ type CountUserFilter struct { } // CountUsers returns number of users. -func CountUsers(opts *CountUserFilter) int64 { - return countUsers(db.DefaultContext, opts) +func CountUsers(ctx context.Context, opts *CountUserFilter) int64 { + return countUsers(ctx, opts) } func countUsers(ctx context.Context, opts *CountUserFilter) int64 { @@ -727,7 +727,7 @@ func countUsers(ctx context.Context, opts *CountUserFilter) int64 { } // GetVerifyUser get user by verify code -func GetVerifyUser(code string) (user *User) { +func GetVerifyUser(ctx context.Context, code string) (user *User) { if len(code) <= base.TimeLimitCodeLength { return nil } @@ -735,7 +735,7 @@ func GetVerifyUser(code string) (user *User) { // use tail hex username query user hexStr := code[base.TimeLimitCodeLength:] if b, err := hex.DecodeString(hexStr); err == nil { - if user, err = GetUserByName(db.DefaultContext, string(b)); user != nil { + if user, err = GetUserByName(ctx, string(b)); user != nil { return user } log.Error("user.getVerifyUser: %v", err) @@ -745,10 +745,10 @@ func GetVerifyUser(code string) (user *User) { } // VerifyUserActiveCode verifies active code when active account -func VerifyUserActiveCode(code string) (user *User) { +func VerifyUserActiveCode(ctx context.Context, code string) (user *User) { minutes := setting.Service.ActiveCodeLives - if user = GetVerifyUser(code); user != nil { + 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) @@ -872,8 +872,8 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error { } // UpdateUserSetting updates user's settings. -func UpdateUserSetting(u *User) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateUserSetting(ctx context.Context, u *User) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -933,7 +933,7 @@ func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) { // GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0 func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) { switch id { - case -1: + case GhostUserID: return NewGhostUser(), nil case ActionsUserID: return NewActionsUser(), nil @@ -949,7 +949,7 @@ func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) { uniqueIDs := container.SetOf(ids...) users := make([]*User, 0, len(ids)) _ = uniqueIDs.Remove(0) - if uniqueIDs.Remove(-1) { + if uniqueIDs.Remove(GhostUserID) { users = append(users, NewGhostUser()) } if uniqueIDs.Remove(ActionsUserID) { @@ -1021,9 +1021,9 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([ } // GetUserNamesByIDs returns usernames for all resolved users from a list of Ids. -func GetUserNamesByIDs(ids []int64) ([]string, error) { +func GetUserNamesByIDs(ctx context.Context, ids []int64) ([]string, error) { unames := make([]string, 0, len(ids)) - err := db.GetEngine(db.DefaultContext).In("id", ids). + err := db.GetEngine(ctx).In("id", ids). Table("user"). Asc("name"). Cols("name"). @@ -1062,9 +1062,9 @@ func GetUserIDsByNames(ctx context.Context, names []string, ignoreNonExistent bo } // GetUsersBySource returns a list of Users for a login source -func GetUsersBySource(s *auth.Source) ([]*User, error) { +func GetUsersBySource(ctx context.Context, s *auth.Source) ([]*User, error) { var users []*User - err := db.GetEngine(db.DefaultContext).Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users) + err := db.GetEngine(ctx).Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users) return users, err } @@ -1145,12 +1145,12 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) { } // GetUser checks if a user already exists -func GetUser(user *User) (bool, error) { - return db.GetEngine(db.DefaultContext).Get(user) +func GetUser(ctx context.Context, user *User) (bool, error) { + return db.GetEngine(ctx).Get(user) } // GetUserByOpenID returns the user object by given OpenID if exists. -func GetUserByOpenID(uri string) (*User, error) { +func GetUserByOpenID(ctx context.Context, uri string) (*User, error) { if len(uri) == 0 { return nil, ErrUserNotExist{0, uri, 0} } @@ -1164,12 +1164,12 @@ func GetUserByOpenID(uri string) (*User, error) { // Otherwise, check in openid table oid := &UserOpenID{} - has, err := db.GetEngine(db.DefaultContext).Where("uri=?", uri).Get(oid) + has, err := db.GetEngine(ctx).Where("uri=?", uri).Get(oid) if err != nil { return nil, err } if has { - return GetUserByID(db.DefaultContext, oid.UID) + return GetUserByID(ctx, oid.UID) } return nil, ErrUserNotExist{0, uri, 0} @@ -1246,7 +1246,7 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { } // If they follow - they see each over - follower := IsFollowing(u.ID, viewer.ID) + follower := IsFollowing(ctx, u.ID, viewer.ID) if follower { return true } @@ -1279,13 +1279,13 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { } // CountWrongUserType count OrgUser who have wrong type -func CountWrongUserType() (int64, error) { - return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User)) +func CountWrongUserType(ctx context.Context) (int64, error) { + return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User)) } // FixWrongUserType fix OrgUser who have wrong type -func FixWrongUserType() (int64, error) { - return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1}) +func FixWrongUserType(ctx context.Context) (int64, error) { + return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1}) } func GetOrderByName() string { diff --git a/models/user/user_system.go b/models/user/user_system.go index f54f4e3ffb..612cdb2cae 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -9,12 +9,18 @@ import ( "code.gitea.io/gitea/modules/structs" ) +const ( + GhostUserID = -1 + GhostUserName = "Ghost" + GhostUserLowerName = "ghost" +) + // NewGhostUser creates and returns a fake user for someone has deleted their account. func NewGhostUser() *User { return &User{ - ID: -1, - Name: "Ghost", - LowerName: "ghost", + ID: GhostUserID, + Name: GhostUserName, + LowerName: GhostUserLowerName, } } @@ -23,13 +29,13 @@ func (u *User) IsGhost() bool { if u == nil { return false } - return u.ID == -1 && u.Name == "Ghost" + return u.ID == GhostUserID && u.Name == GhostUserName } // NewReplaceUser creates and returns a fake user for external user func NewReplaceUser(name string) *User { return &User{ - ID: -1, + ID: 0, Name: name, LowerName: strings.ToLower(name), } diff --git a/models/user/user_test.go b/models/user/user_test.go index 44eaf63556..971117482c 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -39,7 +39,7 @@ func TestGetUserEmailsByNames(t *testing.T) { assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"})) assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"})) - assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user7"})) + assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"})) } func TestCanCreateOrganization(t *testing.T) { @@ -63,7 +63,7 @@ func TestCanCreateOrganization(t *testing.T) { func TestSearchUsers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) { - users, _, err := user_model.SearchUsers(opts) + users, _, err := user_model.SearchUsers(db.DefaultContext, opts) assert.NoError(t, err) cassText := fmt.Sprintf("ids: %v, opts: %v", expectedUserOrOrgIDs, opts) if assert.Len(t, users, len(expectedUserOrOrgIDs), "case: %s", cassText) { @@ -150,16 +150,16 @@ func TestEmailNotificationPreferences(t *testing.T) { assert.Equal(t, test.expected, user.EmailNotifications()) // Try all possible settings - assert.NoError(t, user_model.SetEmailNotifications(user, user_model.EmailNotificationsEnabled)) + assert.NoError(t, user_model.SetEmailNotifications(db.DefaultContext, user, user_model.EmailNotificationsEnabled)) assert.Equal(t, user_model.EmailNotificationsEnabled, user.EmailNotifications()) - assert.NoError(t, user_model.SetEmailNotifications(user, user_model.EmailNotificationsOnMention)) + assert.NoError(t, user_model.SetEmailNotifications(db.DefaultContext, user, user_model.EmailNotificationsOnMention)) assert.Equal(t, user_model.EmailNotificationsOnMention, user.EmailNotifications()) - assert.NoError(t, user_model.SetEmailNotifications(user, user_model.EmailNotificationsDisabled)) + assert.NoError(t, user_model.SetEmailNotifications(db.DefaultContext, user, user_model.EmailNotificationsDisabled)) assert.Equal(t, user_model.EmailNotificationsDisabled, user.EmailNotifications()) - assert.NoError(t, user_model.SetEmailNotifications(user, user_model.EmailNotificationsAndYourOwn)) + assert.NoError(t, user_model.SetEmailNotifications(db.DefaultContext, user, user_model.EmailNotificationsAndYourOwn)) assert.Equal(t, user_model.EmailNotificationsAndYourOwn, user.EmailNotifications()) } } @@ -239,7 +239,7 @@ func TestCreateUserInvalidEmail(t *testing.T) { MustChangePassword: false, } - err := user_model.CreateUser(user) + err := user_model.CreateUser(db.DefaultContext, user) assert.Error(t, err) assert.True(t, user_model.IsErrEmailCharIsNotSupported(err)) } @@ -253,7 +253,7 @@ func TestCreateUserEmailAlreadyUsed(t *testing.T) { user.Name = "testuser" user.LowerName = strings.ToLower(user.Name) user.ID = 0 - err := user_model.CreateUser(user) + err := user_model.CreateUser(db.DefaultContext, user) assert.Error(t, err) assert.True(t, user_model.IsErrEmailAlreadyUsed(err)) } @@ -270,7 +270,7 @@ func TestCreateUserCustomTimestamps(t *testing.T) { user.ID = 0 user.Email = "unique@example.com" user.CreatedUnix = creationTimestamp - err := user_model.CreateUser(user) + err := user_model.CreateUser(db.DefaultContext, user) assert.NoError(t, err) fetched, err := user_model.GetUserByID(context.Background(), user.ID) @@ -295,7 +295,7 @@ func TestCreateUserWithoutCustomTimestamps(t *testing.T) { user.Email = "unique@example.com" user.CreatedUnix = 0 user.UpdatedUnix = 0 - err := user_model.CreateUser(user) + err := user_model.CreateUser(db.DefaultContext, user) assert.NoError(t, err) timestampEnd := time.Now().Unix() @@ -372,9 +372,9 @@ func TestUpdateUser(t *testing.T) { func TestUpdateUserEmailAlreadyUsed(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - user2.Email = user3.Email + user2.Email = org3.Email err := user_model.UpdateUser(db.DefaultContext, user2, true) assert.True(t, user_model.IsErrEmailAlreadyUsed(err)) } @@ -429,17 +429,17 @@ func TestNewUserRedirect3(t *testing.T) { func TestGetUserByOpenID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - _, err := user_model.GetUserByOpenID("https://unknown") + _, err := user_model.GetUserByOpenID(db.DefaultContext, "https://unknown") if assert.Error(t, err) { assert.True(t, user_model.IsErrUserNotExist(err)) } - user, err := user_model.GetUserByOpenID("https://user1.domain1.tld") + user, err := user_model.GetUserByOpenID(db.DefaultContext, "https://user1.domain1.tld") if assert.NoError(t, err) { assert.Equal(t, int64(1), user.ID) } - user, err = user_model.GetUserByOpenID("https://domain1.tld/user2/") + user, err = user_model.GetUserByOpenID(db.DefaultContext, "https://domain1.tld/user2/") if assert.NoError(t, err) { assert.Equal(t, int64(2), user.ID) } @@ -449,13 +449,13 @@ func TestFollowUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(followerID, followedID int64) { - assert.NoError(t, user_model.FollowUser(followerID, followedID)) + assert.NoError(t, user_model.FollowUser(db.DefaultContext, followerID, followedID)) unittest.AssertExistsAndLoadBean(t, &user_model.Follow{UserID: followerID, FollowID: followedID}) } testSuccess(4, 2) testSuccess(5, 2) - assert.NoError(t, user_model.FollowUser(2, 2)) + assert.NoError(t, user_model.FollowUser(db.DefaultContext, 2, 2)) unittest.CheckConsistencyFor(t, &user_model.User{}) } @@ -464,7 +464,7 @@ func TestUnfollowUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(followerID, followedID int64) { - assert.NoError(t, user_model.UnfollowUser(followerID, followedID)) + assert.NoError(t, user_model.UnfollowUser(db.DefaultContext, followerID, followedID)) unittest.AssertNotExistsBean(t, &user_model.Follow{UserID: followerID, FollowID: followedID}) } testSuccess(4, 2) diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go index 3ece5f062d..3ff27b93a7 100644 --- a/models/webhook/hooktask.go +++ b/models/webhook/hooktask.go @@ -101,9 +101,9 @@ func (t *HookTask) simpleMarshalJSON(v any) string { } // HookTasks returns a list of hook tasks by given conditions. -func HookTasks(hookID int64, page int) ([]*HookTask, error) { +func HookTasks(ctx context.Context, hookID int64, page int) ([]*HookTask, error) { tasks := make([]*HookTask, 0, setting.Webhook.PagingNum) - return tasks, db.GetEngine(db.DefaultContext). + return tasks, db.GetEngine(ctx). Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum). Where("hook_id=?", hookID). Desc("id"). @@ -143,8 +143,8 @@ func GetHookTaskByID(ctx context.Context, id int64) (*HookTask, error) { } // UpdateHookTask updates information of hook task. -func UpdateHookTask(t *HookTask) error { - _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) +func UpdateHookTask(ctx context.Context, t *HookTask) error { + _, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t) return err } diff --git a/models/webhook/main_test.go b/models/webhook/main_test.go index 8df569a3cc..f19465d505 100644 --- a/models/webhook/main_test.go +++ b/models/webhook/main_test.go @@ -4,7 +4,6 @@ package webhook import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -12,7 +11,6 @@ import ( func TestMain(m *testing.M) { unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), FixtureFiles: []string{ "webhook.yml", "hook_task.yml", diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index fc2bbed083..b28cea6a9c 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -155,8 +155,8 @@ func (w *Webhook) AfterLoad() { } // History returns history of webhook by given conditions. -func (w *Webhook) History(page int) ([]*HookTask, error) { - return HookTasks(w.ID, page) +func (w *Webhook) History(ctx context.Context, page int) ([]*HookTask, error) { + return HookTasks(ctx, w.ID, page) } // UpdateEvent handles conversion from HookEvent to Events. @@ -394,8 +394,8 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error { // getWebhook uses argument bean as query condition, // ID must be specified and do not assign unnecessary fields. -func getWebhook(bean *Webhook) (*Webhook, error) { - has, err := db.GetEngine(db.DefaultContext).Get(bean) +func getWebhook(ctx context.Context, bean *Webhook) (*Webhook, error) { + has, err := db.GetEngine(ctx).Get(bean) if err != nil { return nil, err } else if !has { @@ -405,23 +405,23 @@ func getWebhook(bean *Webhook) (*Webhook, error) { } // GetWebhookByID returns webhook of repository by given ID. -func GetWebhookByID(id int64) (*Webhook, error) { - return getWebhook(&Webhook{ +func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) { + return getWebhook(ctx, &Webhook{ ID: id, }) } // GetWebhookByRepoID returns webhook of repository by given ID. -func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) { - return getWebhook(&Webhook{ +func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) { + return getWebhook(ctx, &Webhook{ ID: id, RepoID: repoID, }) } // GetWebhookByOwnerID returns webhook of a user or organization by given ID. -func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) { - return getWebhook(&Webhook{ +func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) { + return getWebhook(ctx, &Webhook{ ID: id, OwnerID: ownerID, }) @@ -466,26 +466,26 @@ func ListWebhooksByOpts(ctx context.Context, opts *ListWebhookOptions) ([]*Webho } // CountWebhooksByOpts count webhooks based on options and ignore pagination -func CountWebhooksByOpts(opts *ListWebhookOptions) (int64, error) { - return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Webhook{}) +func CountWebhooksByOpts(ctx context.Context, opts *ListWebhookOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.toCond()).Count(&Webhook{}) } // UpdateWebhook updates information of webhook. -func UpdateWebhook(w *Webhook) error { - _, err := db.GetEngine(db.DefaultContext).ID(w.ID).AllCols().Update(w) +func UpdateWebhook(ctx context.Context, w *Webhook) error { + _, err := db.GetEngine(ctx).ID(w.ID).AllCols().Update(w) return err } // UpdateWebhookLastStatus updates last status of webhook. -func UpdateWebhookLastStatus(w *Webhook) error { - _, err := db.GetEngine(db.DefaultContext).ID(w.ID).Cols("last_status").Update(w) +func UpdateWebhookLastStatus(ctx context.Context, w *Webhook) error { + _, err := db.GetEngine(ctx).ID(w.ID).Cols("last_status").Update(w) return err } // deleteWebhook uses argument bean as query condition, // ID must be specified and do not assign unnecessary fields. -func deleteWebhook(bean *Webhook) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func deleteWebhook(ctx context.Context, bean *Webhook) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -503,16 +503,16 @@ func deleteWebhook(bean *Webhook) (err error) { } // DeleteWebhookByRepoID deletes webhook of repository by given ID. -func DeleteWebhookByRepoID(repoID, id int64) error { - return deleteWebhook(&Webhook{ +func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error { + return deleteWebhook(ctx, &Webhook{ ID: id, RepoID: repoID, }) } // DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID. -func DeleteWebhookByOwnerID(ownerID, id int64) error { - return deleteWebhook(&Webhook{ +func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error { + return deleteWebhook(ctx, &Webhook{ ID: id, OwnerID: ownerID, }) diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index de6568c321..6a01fdd75f 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -33,14 +33,14 @@ func TestIsValidHookContentType(t *testing.T) { func TestWebhook_History(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1}) - tasks, err := webhook.History(0) + tasks, err := webhook.History(db.DefaultContext, 0) assert.NoError(t, err) if assert.Len(t, tasks, 1) { assert.Equal(t, int64(1), tasks[0].ID) } webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) - tasks, err = webhook.History(0) + tasks, err = webhook.History(db.DefaultContext, 0) assert.NoError(t, err) assert.Len(t, tasks, 0) } @@ -101,22 +101,22 @@ func TestCreateWebhook(t *testing.T) { func TestGetWebhookByRepoID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - hook, err := GetWebhookByRepoID(1, 1) + hook, err := GetWebhookByRepoID(db.DefaultContext, 1, 1) assert.NoError(t, err) assert.Equal(t, int64(1), hook.ID) - _, err = GetWebhookByRepoID(unittest.NonexistentID, unittest.NonexistentID) + _, err = GetWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestGetWebhookByOwnerID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - hook, err := GetWebhookByOwnerID(3, 3) + hook, err := GetWebhookByOwnerID(db.DefaultContext, 3, 3) assert.NoError(t, err) assert.Equal(t, int64(3), hook.ID) - _, err = GetWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID) + _, err = GetWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } @@ -167,17 +167,17 @@ func TestUpdateWebhook(t *testing.T) { hook.IsActive = true hook.ContentType = ContentTypeForm unittest.AssertNotExistsBean(t, hook) - assert.NoError(t, UpdateWebhook(hook)) + assert.NoError(t, UpdateWebhook(db.DefaultContext, hook)) unittest.AssertExistsAndLoadBean(t, hook) } func TestDeleteWebhookByRepoID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2, RepoID: 1}) - assert.NoError(t, DeleteWebhookByRepoID(1, 2)) + assert.NoError(t, DeleteWebhookByRepoID(db.DefaultContext, 1, 2)) unittest.AssertNotExistsBean(t, &Webhook{ID: 2, RepoID: 1}) - err := DeleteWebhookByRepoID(unittest.NonexistentID, unittest.NonexistentID) + err := DeleteWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } @@ -185,23 +185,23 @@ func TestDeleteWebhookByRepoID(t *testing.T) { func TestDeleteWebhookByOwnerID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3}) - assert.NoError(t, DeleteWebhookByOwnerID(3, 3)) + assert.NoError(t, DeleteWebhookByOwnerID(db.DefaultContext, 3, 3)) unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3}) - err := DeleteWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID) + err := DeleteWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestHookTasks(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - hookTasks, err := HookTasks(1, 1) + hookTasks, err := HookTasks(db.DefaultContext, 1, 1) assert.NoError(t, err) if assert.Len(t, hookTasks, 1) { assert.Equal(t, int64(1), hookTasks[0].ID) } - hookTasks, err = HookTasks(unittest.NonexistentID, 1) + hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1) assert.NoError(t, err) assert.Len(t, hookTasks, 0) } @@ -225,7 +225,7 @@ func TestUpdateHookTask(t *testing.T) { hook.PayloadContent = "new payload content" hook.IsDelivered = true unittest.AssertNotExistsBean(t, hook) - assert.NoError(t, UpdateHookTask(hook)) + assert.NoError(t, UpdateHookTask(db.DefaultContext, hook)) unittest.AssertExistsAndLoadBean(t, hook) } diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go index fa1b57638f..66b977c01f 100644 --- a/modules/activitypub/client.go +++ b/modules/activitypub/client.go @@ -5,6 +5,7 @@ package activitypub import ( "bytes" + "context" "crypto/rsa" "crypto/x509" "encoding/pem" @@ -61,14 +62,14 @@ type Client struct { } // NewClient function -func NewClient(user *user_model.User, pubID string) (c *Client, err error) { +func NewClient(ctx context.Context, user *user_model.User, pubID string) (c *Client, err error) { if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil { return nil, err } else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil { return nil, err } - priv, err := GetPrivateKey(user) + priv, err := GetPrivateKey(ctx, user) if err != nil { return nil, err } diff --git a/modules/activitypub/client_test.go b/modules/activitypub/client_test.go index 83000b96d5..65ea8d4d5b 100644 --- a/modules/activitypub/client_test.go +++ b/modules/activitypub/client_test.go @@ -11,6 +11,7 @@ import ( "regexp" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" @@ -22,7 +23,7 @@ func TestActivityPubSignedPost(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) pubID := "https://example.com/pubID" - c, err := NewClient(user, pubID) + c, err := NewClient(db.DefaultContext, user, pubID) assert.NoError(t, err) expected := "BODY" diff --git a/modules/activitypub/main_test.go b/modules/activitypub/main_test.go index dcd57bb59e..4591f1fa55 100644 --- a/modules/activitypub/main_test.go +++ b/modules/activitypub/main_test.go @@ -4,7 +4,6 @@ package activitypub import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -15,7 +14,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/modules/activitypub/user_settings.go b/modules/activitypub/user_settings.go index ef9bc0a864..d196a3806e 100644 --- a/modules/activitypub/user_settings.go +++ b/modules/activitypub/user_settings.go @@ -4,6 +4,8 @@ package activitypub import ( + "context" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/util" ) @@ -11,19 +13,19 @@ import ( const rsaBits = 3072 // GetKeyPair function returns a user's private and public keys -func GetKeyPair(user *user_model.User) (pub, priv string, err error) { +func GetKeyPair(ctx context.Context, user *user_model.User) (pub, priv string, err error) { var settings map[string]*user_model.Setting - settings, err = user_model.GetSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem}) + settings, err = user_model.GetSettings(ctx, user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem}) if err != nil { return pub, priv, err } else if len(settings) == 0 { if priv, pub, err = util.GenerateKeyPair(rsaBits); err != nil { return pub, priv, err } - if err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPrivPem, priv); err != nil { + if err = user_model.SetUserSetting(ctx, user.ID, user_model.UserActivityPubPrivPem, priv); err != nil { return pub, priv, err } - if err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPubPem, pub); err != nil { + if err = user_model.SetUserSetting(ctx, user.ID, user_model.UserActivityPubPubPem, pub); err != nil { return pub, priv, err } return pub, priv, err @@ -35,13 +37,13 @@ func GetKeyPair(user *user_model.User) (pub, priv string, err error) { } // GetPublicKey function returns a user's public key -func GetPublicKey(user *user_model.User) (pub string, err error) { - pub, _, err = GetKeyPair(user) +func GetPublicKey(ctx context.Context, user *user_model.User) (pub string, err error) { + pub, _, err = GetKeyPair(ctx, user) return pub, err } // GetPrivateKey function returns a user's private key -func GetPrivateKey(user *user_model.User) (priv string, err error) { - _, priv, err = GetKeyPair(user) +func GetPrivateKey(ctx context.Context, user *user_model.User) (priv string, err error) { + _, priv, err = GetKeyPair(ctx, user) return priv, err } diff --git a/modules/activitypub/user_settings_test.go b/modules/activitypub/user_settings_test.go index 78ebf8e824..2d77906521 100644 --- a/modules/activitypub/user_settings_test.go +++ b/modules/activitypub/user_settings_test.go @@ -6,6 +6,7 @@ package activitypub import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -17,12 +18,12 @@ import ( func TestUserSettings(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - pub, priv, err := GetKeyPair(user1) + pub, priv, err := GetKeyPair(db.DefaultContext, user1) assert.NoError(t, err) - pub1, err := GetPublicKey(user1) + pub1, err := GetPublicKey(db.DefaultContext, user1) assert.NoError(t, err) assert.Equal(t, pub, pub1) - priv1, err := GetPrivateKey(user1) + priv1, err := GetPrivateKey(db.DefaultContext, user1) assert.NoError(t, err) assert.Equal(t, priv, priv1) } diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index e732878f85..189d197333 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -68,7 +68,7 @@ func (u *User) WebAuthnIcon() string { // WebAuthnCredentials implementns the webauthn.User interface func (u *User) WebAuthnCredentials() []webauthn.Credential { - dbCreds, err := auth.GetWebAuthnCredentialsByUID(u.ID) + dbCreds, err := auth.GetWebAuthnCredentialsByUID(db.DefaultContext, u.ID) if err != nil { return nil } diff --git a/modules/context/api.go b/modules/context/api.go index 58532b883d..a46af6ed78 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -101,6 +101,12 @@ type APIRedirect struct{} // swagger:response string type APIString string +// APIRepoArchivedError is an error that is raised when an archived repo should be modified +// swagger:response repoArchivedError +type APIRepoArchivedError struct { + APIError +} + // ServerError responds with error message, status is 500 func (ctx *APIContext) ServerError(title string, err error) { ctx.Error(http.StatusInternalServerError, title, err) @@ -212,7 +218,7 @@ func (ctx *APIContext) CheckForOTP() { } otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") - twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID) + twofa, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID) if err != nil { if auth.IsErrTwoFactorNotEnrolled(err) { return // No 2FA enrollment for this user diff --git a/modules/context/context_cookie.go b/modules/context/context_cookie.go index 9ce67a5298..b6f8dadb56 100644 --- a/modules/context/context_cookie.go +++ b/modules/context/context_cookie.go @@ -4,16 +4,11 @@ package context import ( - "encoding/hex" "net/http" "strings" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" - - "github.com/minio/sha256-simd" - "golang.org/x/crypto/pbkdf2" ) const CookieNameFlash = "gitea_flash" @@ -45,42 +40,3 @@ func (ctx *Context) DeleteSiteCookie(name string) { func (ctx *Context) GetSiteCookie(name string) string { return middleware.GetSiteCookie(ctx.Req, name) } - -// GetSuperSecureCookie returns given cookie value from request header with secret string. -func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) { - val := ctx.GetSiteCookie(name) - return ctx.CookieDecrypt(secret, val) -} - -// CookieDecrypt returns given value from with secret string. -func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) { - if val == "" { - return "", false - } - - text, err := hex.DecodeString(val) - if err != nil { - return "", false - } - - key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New) - text, err = util.AESGCMDecrypt(key, text) - return string(text), err == nil -} - -// SetSuperSecureCookie sets given cookie value to response header with secret string. -func (ctx *Context) SetSuperSecureCookie(secret, name, value string, maxAge int) { - text := ctx.CookieEncrypt(secret, value) - ctx.SetSiteCookie(name, text, maxAge) -} - -// CookieEncrypt encrypts a given value using the provided secret -func (ctx *Context) CookieEncrypt(secret, value string) string { - key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New) - text, err := util.AESGCMEncrypt(key, []byte(value)) - if err != nil { - panic("error encrypting cookie: " + err.Error()) - } - - return hex.EncodeToString(text) -} diff --git a/modules/context/org.go b/modules/context/org.go index 7638ffdd3f..d068646577 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -46,7 +46,7 @@ func GetOrganizationByParams(ctx *Context) { ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName) if err != nil { if organization.IsErrOrgNotExist(err) { - redirectUserID, err := user_model.LookupUserRedirect(orgName) + redirectUserID, err := user_model.LookupUserRedirect(ctx, orgName) if err == nil { RedirectToUser(ctx.Base, orgName, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { @@ -128,7 +128,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { ctx.Org.IsTeamAdmin = true ctx.Org.CanCreateOrgRepo = true } else if ctx.IsSigned { - ctx.Org.IsOwner, err = org.IsOwnedBy(ctx.Doer.ID) + ctx.Org.IsOwner, err = org.IsOwnedBy(ctx, ctx.Doer.ID) if err != nil { ctx.ServerError("IsOwnedBy", err) return @@ -140,12 +140,12 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { ctx.Org.IsTeamAdmin = true ctx.Org.CanCreateOrgRepo = true } else { - ctx.Org.IsMember, err = org.IsOrgMember(ctx.Doer.ID) + ctx.Org.IsMember, err = org.IsOrgMember(ctx, ctx.Doer.ID) if err != nil { ctx.ServerError("IsOrgMember", err) return } - ctx.Org.CanCreateOrgRepo, err = org.CanCreateOrgRepo(ctx.Doer.ID) + ctx.Org.CanCreateOrgRepo, err = org.CanCreateOrgRepo(ctx, ctx.Doer.ID) if err != nil { ctx.ServerError("CanCreateOrgRepo", err) return @@ -165,7 +165,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsPublicMember"] = func(uid int64) bool { - is, _ := organization.IsPublicMembership(ctx.Org.Organization.ID, uid) + is, _ := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, uid) return is } ctx.Data["CanCreateOrgRepo"] = ctx.Org.CanCreateOrgRepo @@ -179,7 +179,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { OrgID: org.ID, PublicOnly: ctx.Org.PublicMemberOnly, } - ctx.Data["NumMembers"], err = organization.CountOrgMembers(opts) + ctx.Data["NumMembers"], err = organization.CountOrgMembers(ctx, opts) if err != nil { ctx.ServerError("CountOrgMembers", err) return @@ -191,7 +191,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { if ctx.Org.IsOwner { shouldSeeAllTeams = true } else { - teams, err := org.GetUserTeams(ctx.Doer.ID) + teams, err := org.GetUserTeams(ctx, ctx.Doer.ID) if err != nil { ctx.ServerError("GetUserTeams", err) return @@ -204,13 +204,13 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { } } if shouldSeeAllTeams { - ctx.Org.Teams, err = org.LoadTeams() + ctx.Org.Teams, err = org.LoadTeams(ctx) if err != nil { ctx.ServerError("LoadTeams", err) return } } else { - ctx.Org.Teams, err = org.GetUserTeams(ctx.Doer.ID) + ctx.Org.Teams, err = org.GetUserTeams(ctx, ctx.Doer.ID) if err != nil { ctx.ServerError("GetUserTeams", err) return diff --git a/modules/context/package.go b/modules/context/package.go index c0813fb2da..87e817c1cd 100644 --- a/modules/context/package.go +++ b/modules/context/package.go @@ -109,7 +109,7 @@ func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.A if doer != nil && !doer.IsGhost() { // 1. If user is logged in, check all team packages permissions var err error - accessMode, err = org.GetOrgUserMaxAuthorizeLevel(doer.ID) + accessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx, doer.ID) if err != nil { return accessMode, err } diff --git a/modules/context/repo.go b/modules/context/repo.go index 8a16d311b1..9efa2ab3c0 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -144,18 +144,18 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use } // CanUseTimetracker returns whether or not a user can use the timetracker. -func (r *Repository) CanUseTimetracker(issue *issues_model.Issue, user *user_model.User) bool { +func (r *Repository) CanUseTimetracker(ctx context.Context, issue *issues_model.Issue, user *user_model.User) bool { // Checking for following: // 1. Is timetracker enabled // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? - isAssigned, _ := issues_model.IsUserAssignedToIssue(db.DefaultContext, issue, user) - return r.Repository.IsTimetrackerEnabled(db.DefaultContext) && (!r.Repository.AllowOnlyContributorsToTrackTime(db.DefaultContext) || + isAssigned, _ := issues_model.IsUserAssignedToIssue(ctx, issue, user) + return r.Repository.IsTimetrackerEnabled(ctx) && (!r.Repository.AllowOnlyContributorsToTrackTime(ctx) || r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned) } // CanCreateIssueDependencies returns whether or not a user can create dependencies. -func (r *Repository) CanCreateIssueDependencies(user *user_model.User, isPull bool) bool { - return r.Repository.IsDependenciesEnabled(db.DefaultContext) && r.Permission.CanWriteIssuesOrPulls(isPull) +func (r *Repository) CanCreateIssueDependencies(ctx context.Context, user *user_model.User, isPull bool) bool { + return r.Repository.IsDependenciesEnabled(ctx) && r.Permission.CanWriteIssuesOrPulls(isPull) } // GetCommitsCount returns cached commit count for current view @@ -456,7 +456,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { return nil } - if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil { + if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil { RedirectToUser(ctx.Base, userName, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { ctx.NotFound("GetUserByName", nil) @@ -495,10 +495,10 @@ func RepoAssignment(ctx *Context) context.CancelFunc { } // Get repository. - repo, err := repo_model.GetRepositoryByName(owner.ID, repoName) + repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName) if err != nil { if repo_model.IsErrRepoNotExist(err) { - redirectRepoID, err := repo_model.LookupRedirect(owner.ID, repoName) + redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, repoName) if err == nil { RedirectToRepo(ctx.Base, redirectRepoID) } else if repo_model.IsErrRedirectNotExist(err) { @@ -545,7 +545,10 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.ServerError("GetReleaseCountByRepoID", err) return nil } - ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{}) + ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ + // only show draft releases for users who can write, read-only users shouldn't see draft releases. + IncludeDrafts: ctx.Repo.CanWrite(unit_model.TypeReleases), + }) if err != nil { ctx.ServerError("GetReleaseCountByRepoID", err) return nil @@ -561,7 +564,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues) ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests) - canSignedUserFork, err := repo_module.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository) + canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository) if err != nil { ctx.ServerError("CanUserForkRepo", err) return nil @@ -598,7 +601,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { } if ctx.IsSigned { - ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx.Doer.ID, repo.ID) + ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, repo.ID) ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID) } @@ -703,18 +706,18 @@ func RepoAssignment(ctx *Context) context.CancelFunc { // People who have push access or have forked repository can propose a new pull request. canPush := ctx.Repo.CanWrite(unit_model.TypeCode) || - (ctx.IsSigned && repo_model.HasForkedRepo(ctx.Doer.ID, ctx.Repo.Repository.ID)) + (ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)) canCompare := false // Pull request is allowed if this is a fork repository // and base repository accepts pull requests. - if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() { + if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls(ctx) { canCompare = true ctx.Data["BaseRepo"] = repo.BaseRepo ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo ctx.Repo.PullRequest.Allowed = canPush ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Repo.Owner.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName) - } else if repo.AllowsPulls() { + } else if repo.AllowsPulls(ctx) { // Or, this is repository accepts pull requests between branches. canCompare = true ctx.Data["BaseRepo"] = repo @@ -740,7 +743,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["RepoTransfer"] = repoTransfer if ctx.Doer != nil { - ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.Doer) + ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) } } @@ -773,6 +776,8 @@ const ( RepoRefBlob ) +const headRefName = "HEAD" + // RepoRef handles repository reference names when the ref name is not // explicitly given func RepoRef() func(*Context) context.CancelFunc { @@ -833,6 +838,14 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { case RepoRefBranch: ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist) if len(ref) == 0 { + + // check if ref is HEAD + parts := strings.Split(path, "/") + if parts[0] == headRefName { + repo.TreePath = strings.Join(parts[1:], "/") + return repo.Repository.DefaultBranch + } + // maybe it's a renamed branch return getRefNameFromPath(ctx, repo, path, func(s string) bool { b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s) @@ -861,6 +874,16 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { repo.TreePath = strings.Join(parts[1:], "/") return parts[0] } + + if len(parts) > 0 && parts[0] == headRefName { + // HEAD ref points to last default branch commit + commit, err := repo.GitRepo.GetBranchCommit(repo.Repository.DefaultBranch) + if err != nil { + return "" + } + repo.TreePath = strings.Join(parts[1:], "/") + return commit.ID.String() + } case RepoRefBlob: _, err := repo.GitRepo.GetBlob(path) if err != nil { diff --git a/modules/contexttest/context_tests.go b/modules/contexttest/context_tests.go index ea91bc5001..8994c1e451 100644 --- a/modules/contexttest/context_tests.go +++ b/modules/contexttest/context_tests.go @@ -83,8 +83,7 @@ func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) { ctx.Repo = repo doer = ctx.Doer default: - assert.Fail(t, "context is not *context.Context or *context.APIContext") - return + assert.FailNow(t, "context is not *context.Context or *context.APIContext") } repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) @@ -105,8 +104,7 @@ func LoadRepoCommit(t *testing.T, ctx gocontext.Context) { case *context.APIContext: repo = ctx.Repo default: - assert.Fail(t, "context is not *context.Context or *context.APIContext") - return + assert.FailNow(t, "context is not *context.Context or *context.APIContext") } gitRepo, err := git.OpenRepository(ctx, repo.Repository.RepoPath()) @@ -130,8 +128,7 @@ func LoadUser(t *testing.T, ctx gocontext.Context, userID int64) { case *context.APIContext: ctx.Doer = doer default: - assert.Fail(t, "context is not *context.Context or *context.APIContext") - return + assert.FailNow(t, "context is not *context.Context or *context.APIContext") } } diff --git a/modules/doctor/authorizedkeys.go b/modules/doctor/authorizedkeys.go index e4d85c4a18..050a4e7974 100644 --- a/modules/doctor/authorizedkeys.go +++ b/modules/doctor/authorizedkeys.go @@ -33,7 +33,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e return fmt.Errorf("Unable to open authorized_keys file. ERROR: %w", err) } logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err) - if err = asymkey_model.RewriteAllPublicKeys(); err != nil { + if err = asymkey_model.RewriteAllPublicKeys(ctx); err != nil { logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err) return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err) } @@ -76,7 +76,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`) } logger.Warn("authorized_keys is out of date. Attempting rewrite...") - err = asymkey_model.RewriteAllPublicKeys() + err = asymkey_model.RewriteAllPublicKeys(ctx) if err != nil { logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err) return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err) diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go index 541fc736f4..e5fc5785e8 100644 --- a/modules/doctor/dbconsistency.go +++ b/modules/doctor/dbconsistency.go @@ -101,7 +101,7 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er }, // find releases without existing repository genericOrphanCheck("Orphaned Releases without existing repository", - "release", "repository", "release.repo_id=repository.id"), + "release", "repository", "`release`.repo_id=repository.id"), // find pulls without existing issues genericOrphanCheck("Orphaned PullRequests without existing issue", "pull_request", "issue", "pull_request.issue_id=issue.id"), @@ -168,9 +168,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er // find protected branches without existing repository genericOrphanCheck("Protected Branches without existing repository", "protected_branch", "repository", "protected_branch.repo_id=repository.id"), - // find deleted branches without existing repository - genericOrphanCheck("Deleted Branches without existing repository", - "deleted_branch", "repository", "deleted_branch.repo_id=repository.id"), + // find branches without existing repository + genericOrphanCheck("Branches without existing repository", + "branch", "repository", "branch.repo_id=repository.id"), // find LFS locks without existing repository genericOrphanCheck("LFS locks without existing repository", "lfs_lock", "repository", "lfs_lock.repo_id=repository.id"), @@ -189,6 +189,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er // find action without repository genericOrphanCheck("Action entries without existing repository", "action", "repository", "action.repo_id=repository.id"), + // find action without user + genericOrphanCheck("Action entries without existing user", + "action", "user", "action.act_user_id=`user`.id"), // find OAuth2Grant without existing user genericOrphanCheck("Orphaned OAuth2Grant without existing User", "oauth2_grant", "user", "oauth2_grant.user_id=`user`.id"), diff --git a/modules/doctor/fix16961.go b/modules/doctor/fix16961.go index 562c78dd76..d3f36d8d5c 100644 --- a/modules/doctor/fix16961.go +++ b/modules/doctor/fix16961.go @@ -290,7 +290,7 @@ func fixBrokenRepoUnits16961(ctx context.Context, logger log.Logger, autofix boo return nil } - return repo_model.UpdateRepoUnit(repoUnit) + return repo_model.UpdateRepoUnit(ctx, repoUnit) }, ) if err != nil { diff --git a/modules/doctor/fix8312.go b/modules/doctor/fix8312.go index 8de3113663..4fc049873a 100644 --- a/modules/doctor/fix8312.go +++ b/modules/doctor/fix8312.go @@ -29,7 +29,7 @@ func fixOwnerTeamCreateOrgRepo(ctx context.Context, logger log.Logger, autofix b return nil } - return models.UpdateTeam(team, false, false) + return models.UpdateTeam(ctx, team, false, false) }, ) if err != nil { diff --git a/modules/doctor/mergebase.go b/modules/doctor/mergebase.go index e79369e581..de460c4190 100644 --- a/modules/doctor/mergebase.go +++ b/modules/doctor/mergebase.go @@ -74,7 +74,7 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro pr.MergeBase = strings.TrimSpace(pr.MergeBase) if pr.MergeBase != oldMergeBase { if autofix { - if err := pr.UpdateCols("merge_base"); err != nil { + if err := pr.UpdateCols(ctx, "merge_base"); err != nil { logger.Critical("Failed to update merge_base. ERROR: %v", err) return fmt.Errorf("Failed to update merge_base. ERROR: %w", err) } diff --git a/modules/doctor/misc.go b/modules/doctor/misc.go index e01c3e109b..f0b5966b54 100644 --- a/modules/doctor/misc.go +++ b/modules/doctor/misc.go @@ -74,7 +74,7 @@ func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error { func checkUserStarNum(ctx context.Context, logger log.Logger, autofix bool) error { if autofix { - if err := models.DoctorUserStarNum(); err != nil { + if err := models.DoctorUserStarNum(ctx); err != nil { logger.Critical("Unable update User Stars numbers") return err } diff --git a/modules/doctor/repository.go b/modules/doctor/repository.go new file mode 100644 index 0000000000..aa3f064ddd --- /dev/null +++ b/modules/doctor/repository.go @@ -0,0 +1,70 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package doctor + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + repo_service "code.gitea.io/gitea/services/repository" + + "xorm.io/builder" +) + +func handleDeleteOrphanedRepos(ctx context.Context, logger log.Logger, autofix bool) error { + test := &consistencyCheck{ + Name: "Repos with no existing owner", + Counter: countOrphanedRepos, + Fixer: deleteOrphanedRepos, + FixedMessage: "Deleted all content related to orphaned repos", + } + return test.Run(ctx, logger, autofix) +} + +// countOrphanedRepos count repository where user of owner_id do not exist +func countOrphanedRepos(ctx context.Context) (int64, error) { + return db.CountOrphanedObjects(ctx, "repository", "user", "repository.owner_id=user.id") +} + +// deleteOrphanedRepos delete repository where user of owner_id do not exist +func deleteOrphanedRepos(ctx context.Context) (int64, error) { + batchSize := db.MaxBatchInsertSize("repository") + e := db.GetEngine(ctx) + var deleted int64 + adminUser := &user_model.User{IsAdmin: true} + + for { + var ids []int64 + if err := e.Table("`repository`"). + Join("LEFT", "`user`", "repository.owner_id=user.id"). + Where(builder.IsNull{"`user`.id"}). + Select("`repository`.id").Limit(batchSize).Find(&ids); err != nil { + return deleted, err + } + + // if we don't get ids we have deleted them all + if len(ids) == 0 { + return deleted, nil + } + + for _, id := range ids { + if err := repo_service.DeleteRepositoryDirectly(ctx, adminUser, id, true); err != nil { + return deleted, err + } + deleted++ + } + } +} + +func init() { + Register(&Check{ + Title: "Deleted all content related to orphaned repos", + Name: "delete-orphaned-repos", + IsDefault: false, + Run: handleDeleteOrphanedRepos, + Priority: 4, + }) +} diff --git a/modules/doctor/usertype.go b/modules/doctor/usertype.go index 550e536cbd..ab32b78e62 100644 --- a/modules/doctor/usertype.go +++ b/modules/doctor/usertype.go @@ -11,14 +11,14 @@ import ( ) func checkUserType(ctx context.Context, logger log.Logger, autofix bool) error { - count, err := user_model.CountWrongUserType() + count, err := user_model.CountWrongUserType(ctx) if err != nil { logger.Critical("Error: %v whilst counting wrong user types") return err } if count > 0 { if autofix { - if count, err = user_model.FixWrongUserType(); err != nil { + if count, err = user_model.FixWrongUserType(ctx); err != nil { logger.Critical("Error: %v whilst fixing wrong user types") return err } diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 35dfc62f1e..f66dc78c7e 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -71,7 +71,7 @@ loop: now := timeutil.TimeStampNow().Add(-2) - uidCounts, err := activities_model.GetUIDsAndNotificationCounts(then, now) + uidCounts, err := activities_model.GetUIDsAndNotificationCounts(ctx, then, now) if err != nil { log.Error("Unable to get UIDcounts: %v", err) } @@ -84,14 +84,14 @@ loop: then = now if setting.Service.EnableTimetracking { - usersStopwatches, err := issues_model.GetUIDsAndStopwatch() + usersStopwatches, err := issues_model.GetUIDsAndStopwatch(ctx) if err != nil { log.Error("Unable to get GetUIDsAndStopwatch: %v", err) return } for _, userStopwatches := range usersStopwatches { - apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches) + apiSWs, err := convert.ToStopWatches(ctx, userStopwatches.StopWatches) if err != nil { if !issues_model.IsErrIssueNotExist(err) { log.Error("Unable to APIFormat stopwatches: %v", err) diff --git a/modules/git/blame.go b/modules/git/blame.go index 4bd13dc32d..6728a6bed8 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -13,6 +13,7 @@ import ( "regexp" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) // BlamePart represents block of blame - continuous lines with one sha @@ -23,12 +24,16 @@ type BlamePart struct { // BlameReader returns part of file blame one by one type BlameReader struct { - cmd *Command output io.WriteCloser reader io.ReadCloser bufferedReader *bufio.Reader done chan error lastSha *string + ignoreRevsFile *string +} + +func (r *BlameReader) UsesIgnoreRevs() bool { + return r.ignoreRevsFile != nil } var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})") @@ -101,28 +106,44 @@ func (r *BlameReader) Close() error { r.bufferedReader = nil _ = r.reader.Close() _ = r.output.Close() + if r.ignoreRevsFile != nil { + _ = util.Remove(*r.ignoreRevsFile) + } return err } // CreateBlameReader creates reader for given repository, commit and file -func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) { - cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain"). - AddDynamicArguments(commitID). +func CreateBlameReader(ctx context.Context, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { + var ignoreRevsFile *string + if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { + ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) + } + + cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain") + if ignoreRevsFile != nil { + // Possible improvement: use --ignore-revs-file /dev/stdin on unix + // 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)) reader, stdout, err := os.Pipe() if err != nil { + if ignoreRevsFile != nil { + _ = util.Remove(*ignoreRevsFile) + } return nil, err } done := make(chan error, 1) - go func(cmd *Command, dir string, stdout io.WriteCloser, done chan error) { + go func() { stderr := bytes.Buffer{} // TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close" err := cmd.Run(&RunOpts{ UseContextTimeout: true, - Dir: dir, + Dir: repoPath, Stdout: stdout, Stderr: &stderr, }) @@ -131,15 +152,42 @@ func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*B if err != nil { log.Error("Error running git blame (dir: %v): %v, stderr: %v", repoPath, err, stderr.String()) } - }(cmd, repoPath, stdout, done) + }() bufferedReader := bufio.NewReader(reader) return &BlameReader{ - cmd: cmd, output: stdout, reader: reader, bufferedReader: bufferedReader, done: done, + ignoreRevsFile: ignoreRevsFile, }, nil } + +func tryCreateBlameIgnoreRevsFile(commit *Commit) *string { + entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs") + if err != nil { + return nil + } + + r, err := entry.Blob().DataAsync() + if err != nil { + return nil + } + defer r.Close() + + f, err := os.CreateTemp("", "gitea_git-blame-ignore-revs") + if err != nil { + return nil + } + + _, err = io.Copy(f, r) + _ = f.Close() + if err != nil { + _ = util.Remove(f.Name()) + return nil + } + + return util.ToPointer(f.Name()) +} diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 1c0cd5c4aa..013350ac2f 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -14,27 +14,127 @@ func TestReadingBlameOutput(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo5_pulls", "f32b0a9dfd09a60f616f29158f772cedd89942d2", "README.md") - assert.NoError(t, err) - defer blameReader.Close() - - parts := []*BlamePart{ - { - "72866af952e98d02a73003501836074b286a78f6", - []string{ - "# test_repo", - "Test repository for testing migration from github to gitea", - }, - }, - { - "f32b0a9dfd09a60f616f29158f772cedd89942d2", - []string{"", "Do not make any changes to this repo it is used for unit testing"}, - }, - } - - for _, part := range parts { - actualPart, err := blameReader.NextPart() + t.Run("Without .git-blame-ignore-revs", func(t *testing.T) { + repo, err := OpenRepository(ctx, "./tests/repos/repo5_pulls") assert.NoError(t, err) - assert.Equal(t, part, actualPart) - } + defer repo.Close() + + commit, err := repo.GetCommit("f32b0a9dfd09a60f616f29158f772cedd89942d2") + assert.NoError(t, err) + + parts := []*BlamePart{ + { + "72866af952e98d02a73003501836074b286a78f6", + []string{ + "# test_repo", + "Test repository for testing migration from github to gitea", + }, + }, + { + "f32b0a9dfd09a60f616f29158f772cedd89942d2", + []string{"", "Do not make any changes to this repo it is used for unit testing"}, + }, + } + + for _, bypass := range []bool{false, true} { + blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo5_pulls", commit, "README.md", bypass) + assert.NoError(t, err) + assert.NotNil(t, blameReader) + defer blameReader.Close() + + assert.False(t, blameReader.UsesIgnoreRevs()) + + for _, part := range parts { + actualPart, err := blameReader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part, actualPart) + } + + // make sure all parts have been read + actualPart, err := blameReader.NextPart() + assert.Nil(t, actualPart) + assert.NoError(t, err) + } + }) + + t.Run("With .git-blame-ignore-revs", func(t *testing.T) { + repo, err := OpenRepository(ctx, "./tests/repos/repo6_blame") + assert.NoError(t, err) + defer repo.Close() + + full := []*BlamePart{ + { + "af7486bd54cfc39eea97207ca666aa69c9d6df93", + []string{"line", "line"}, + }, + { + "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + []string{"changed line"}, + }, + { + "af7486bd54cfc39eea97207ca666aa69c9d6df93", + []string{"line", "line", ""}, + }, + } + + cases := []struct { + CommitID string + UsesIgnoreRevs bool + Bypass bool + Parts []*BlamePart + }{ + { + CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", + UsesIgnoreRevs: true, + Bypass: false, + Parts: []*BlamePart{ + { + "af7486bd54cfc39eea97207ca666aa69c9d6df93", + []string{"line", "line", "changed line", "line", "line", ""}, + }, + }, + }, + { + CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", + UsesIgnoreRevs: false, + Bypass: true, + Parts: full, + }, + { + CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + UsesIgnoreRevs: false, + Bypass: false, + Parts: full, + }, + { + CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + UsesIgnoreRevs: false, + Bypass: false, + Parts: full, + }, + } + + for _, c := range cases { + commit, err := repo.GetCommit(c.CommitID) + assert.NoError(t, err) + + blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass) + assert.NoError(t, err) + assert.NotNil(t, blameReader) + defer blameReader.Close() + + assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs()) + + for _, part := range c.Parts { + actualPart, err := blameReader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part, actualPart) + } + + // make sure all parts have been read + actualPart, err := blameReader.NextPart() + assert.Nil(t, actualPart) + assert.NoError(t, err) + } + }) } diff --git a/modules/git/repo.go b/modules/git/repo.go index 5681d39c3b..32f0e7007e 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -86,7 +86,8 @@ func (repo *Repository) IsEmpty() (bool, error) { Stdout: &output, Stderr: &errbuf, }); err != nil { - if err.Error() == "exit status 1" && errbuf.String() == "" { + if (err.Error() == "exit status 1" && strings.TrimSpace(errbuf.String()) == "") || err.Error() == "exit status 129" { + // git 2.11 exits with 129 if the repo is empty return true, nil } return true, fmt.Errorf("check empty: %w - %s", err, errbuf.String()) diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index 350b69dd25..ed16dccbe4 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -27,7 +27,7 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { assert.Equal(t, "linguist-vendored", attr.Attribute) assert.Equal(t, "unspecified", attr.Value) case <-time.After(100 * time.Millisecond): - assert.Fail(t, "took too long to read an attribute from the list") + assert.FailNow(t, "took too long to read an attribute from the list") } // Write a second attribute again n, err = wr.Write([]byte(testStr)) @@ -41,7 +41,7 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { assert.Equal(t, "linguist-vendored", attr.Attribute) assert.Equal(t, "unspecified", attr.Value) case <-time.After(100 * time.Millisecond): - assert.Fail(t, "took too long to read an attribute from the list") + assert.FailNow(t, "took too long to read an attribute from the list") } // Write a partial attribute @@ -52,14 +52,14 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { select { case <-wr.ReadAttribute(): - assert.Fail(t, "There should not be an attribute ready to read") + assert.FailNow(t, "There should not be an attribute ready to read") case <-time.After(100 * time.Millisecond): } _, err = wr.Write([]byte("attribute\x00")) assert.NoError(t, err) select { case <-wr.ReadAttribute(): - assert.Fail(t, "There should not be an attribute ready to read") + assert.FailNow(t, "There should not be an attribute ready to read") case <-time.After(100 * time.Millisecond): } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 6fc3063629..6b06fef656 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -116,17 +116,13 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co c.AddArguments("-i") // add authors if present in search query - if len(opts.Authors) > 0 { - for _, v := range opts.Authors { - c.AddOptionFormat("--author=%s", v) - } + for _, v := range opts.Authors { + c.AddOptionFormat("--author=%s", v) } // add committers if present in search query - if len(opts.Committers) > 0 { - for _, v := range opts.Committers { - c.AddOptionFormat("--committer=%s", v) - } + for _, v := range opts.Committers { + c.AddOptionFormat("--committer=%s", v) } // add time constraints if present in search query @@ -150,10 +146,8 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co // add remaining keywords from search string // note this is done only for command created above - if len(opts.Keywords) > 0 { - for _, v := range opts.Keywords { - cmd.AddOptionFormat("--grep=%s", v) - } + for _, v := range opts.Keywords { + cmd.AddOptionFormat("--grep=%s", v) } // search for commits matching given constraints and keywords in commit msg @@ -168,25 +162,23 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co // if there are any keywords (ie not committer:, author:, time:) // then let's iterate over them - if len(opts.Keywords) > 0 { - for _, v := range opts.Keywords { - // ignore anything not matching a valid sha pattern - if IsValidSHAPattern(v) { - // create new git log command with 1 commit limit - hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat) - // add previous arguments except for --grep and --all - addCommonSearchArgs(hashCmd) - // add keyword as - hashCmd.AddDynamicArguments(v) + for _, v := range opts.Keywords { + // ignore anything not matching a valid sha pattern + if IsValidSHAPattern(v) { + // create new git log command with 1 commit limit + hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat) + // add previous arguments except for --grep and --all + addCommonSearchArgs(hashCmd) + // add keyword as + hashCmd.AddDynamicArguments(v) - // search with given constraints for commit matching sha hash of v - hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path}) - if err != nil || bytes.Contains(stdout, hashMatching) { - continue - } - stdout = append(stdout, hashMatching...) - stdout = append(stdout, '\n') + // search with given constraints for commit matching sha hash of v + hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path}) + if err != nil || bytes.Contains(stdout, hashMatching) { + continue } + stdout = append(stdout, hashMatching...) + stdout = append(stdout, '\n') } } diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index fb6a7cdd1a..4d94efd1ac 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -71,7 +71,6 @@ func TestRepository_GetTag(t *testing.T) { if lTag == nil { assert.NotNil(t, lTag) assert.FailNow(t, "nil lTag: %s", lTagName) - return } assert.EqualValues(t, lTagName, lTag.Name) assert.EqualValues(t, lTagCommitID, lTag.ID.String()) @@ -105,7 +104,6 @@ func TestRepository_GetTag(t *testing.T) { if aTag == nil { assert.NotNil(t, aTag) assert.FailNow(t, "nil aTag: %s", aTagName) - return } assert.EqualValues(t, aTagName, aTag.Name) assert.EqualValues(t, aTagID, aTag.ID.String()) diff --git a/tests/gitea-repositories-meta/user3/repo3.git/HEAD b/modules/git/tests/repos/repo6_blame/HEAD similarity index 100% rename from tests/gitea-repositories-meta/user3/repo3.git/HEAD rename to modules/git/tests/repos/repo6_blame/HEAD diff --git a/modules/git/tests/repos/repo6_blame/config b/modules/git/tests/repos/repo6_blame/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643c b/modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643c new file mode 100644 index 0000000000..6cde9108e7 Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643c differ diff --git a/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1 b/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1 new file mode 100644 index 0000000000..b8db01dc35 Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1 differ diff --git a/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376 b/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376 new file mode 100644 index 0000000000..6c0ae4723f Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376 differ diff --git a/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9 b/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9 new file mode 100644 index 0000000000..5c2b5641cf Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9 differ diff --git a/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7 b/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7 new file mode 100644 index 0000000000..3c6471864c Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7 differ diff --git a/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044 b/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044 new file mode 100644 index 0000000000..847b7bc305 Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044 differ diff --git a/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93 b/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93 new file mode 100644 index 0000000000..206ef1efb7 Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93 differ diff --git a/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421 b/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421 new file mode 100644 index 0000000000..bb26889ed3 Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421 differ diff --git a/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8 b/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8 new file mode 100644 index 0000000000..1653ed9544 Binary files /dev/null and b/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8 differ diff --git a/modules/git/tests/repos/repo6_blame/refs/heads/master b/modules/git/tests/repos/repo6_blame/refs/heads/master new file mode 100644 index 0000000000..01c9922c5f --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/refs/heads/master @@ -0,0 +1 @@ +544d8f7a3b15927cddf2299b4b562d6ebd71b6a7 diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go index 748f7f3075..e48fef8b9d 100644 --- a/modules/gitgraph/graph_models.go +++ b/modules/gitgraph/graph_models.go @@ -116,10 +116,10 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_ c.Verification = asymkey_model.ParseCommitWithSignature(ctx, c.Commit) _ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(repository, user.ID) + return repo_model.IsOwnerMemberCollaborator(ctx, repository, user.ID) }, &keyMap) - statuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repository.ID, c.Commit.ID.String(), db.ListOptions{}) + statuses, _, err := git_model.GetLatestCommitStatus(ctx, repository.ID, c.Commit.ID.String(), db.ListOptions{}) if err != nil { log.Error("GetLatestCommitStatus: %v", err) } else { diff --git a/modules/hostmatcher/http.go b/modules/hostmatcher/http.go index 65f5f78b14..c743f6efb3 100644 --- a/modules/hostmatcher/http.go +++ b/modules/hostmatcher/http.go @@ -7,12 +7,17 @@ import ( "context" "fmt" "net" + "net/url" "syscall" "time" ) // NewDialContext returns a DialContext for Transport, the DialContext will do allow/block list check func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx context.Context, network, addr string) (net.Conn, error) { + return NewDialContextWithProxy(usage, allowList, blockList, nil) +} + +func NewDialContextWithProxy(usage string, allowList, blockList *HostMatchList, proxy *url.URL) func(ctx context.Context, network, addr string) (net.Conn, error) { // How Go HTTP Client works with redirection: // transport.RoundTrip URL=http://domain.com, Host=domain.com // transport.DialContext addrOrHost=domain.com:80 @@ -26,11 +31,18 @@ func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, - Control: func(network, ipAddr string, c syscall.RawConn) (err error) { - var host string - if host, _, err = net.SplitHostPort(addrOrHost); err != nil { + Control: func(network, ipAddr string, c syscall.RawConn) error { + host, port, err := net.SplitHostPort(addrOrHost) + if err != nil { return err } + if proxy != nil { + // Always allow the host of the proxy, but only on the specified port. + if host == proxy.Hostname() && port == proxy.Port() { + return nil + } + } + // in Control func, the addr was already resolved to IP:PORT format, there is no cost to do ResolveTCPAddr here tcpAddr, err := net.ResolveTCPAddr(network, ipAddr) if err != nil { diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go index 1ba6b849d1..e4686fa01f 100644 --- a/modules/indexer/code/git.go +++ b/modules/indexer/code/git.go @@ -30,7 +30,14 @@ func getRepoChanges(ctx context.Context, repo *repo_model.Repository, revision s return nil, err } - if len(status.CommitSha) == 0 { + needGenesis := len(status.CommitSha) == 0 + if !needGenesis { + hasAncestorCmd := git.NewCommand(ctx, "merge-base").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision) + stdout, _, _ := hasAncestorCmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + needGenesis = len(stdout) == 0 + } + + if needGenesis { return genesisChanges(ctx, repo, revision) } return nonGenesisChanges(ctx, repo, revision) diff --git a/modules/indexer/code/indexer.go b/modules/indexer/code/indexer.go index 019773fe51..ebebf6ba8a 100644 --- a/modules/indexer/code/indexer.go +++ b/modules/indexer/code/indexer.go @@ -288,7 +288,7 @@ func populateRepoIndexer(ctx context.Context) { return default: } - ids, err := repo_model.GetUnindexedRepos(repo_model.RepoIndexerTypeCode, maxRepoID, 0, 50) + ids, err := repo_model.GetUnindexedRepos(ctx, repo_model.RepoIndexerTypeCode, maxRepoID, 0, 50) if err != nil { log.Error("populateRepoIndexer: %v", err) return diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index 2a650646bd..5eb8e61e3d 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -6,7 +6,6 @@ package code import ( "context" "os" - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -23,9 +22,7 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", "..", ".."), - }) + unittest.MainTest(m) } func testIndexer(name string, t *testing.T, indexer internal.Indexer) { @@ -99,11 +96,10 @@ func TestBleveIndexAndSearch(t *testing.T) { idx := bleve.NewIndexer(dir) _, err := idx.Init(context.Background()) if err != nil { - assert.Fail(t, "Unable to create bleve indexer Error: %v", err) if idx != nil { idx.Close() } - return + assert.FailNow(t, "Unable to create bleve indexer Error: %v", err) } defer idx.Close() @@ -121,11 +117,10 @@ func TestESIndexAndSearch(t *testing.T) { indexer := elasticsearch.NewIndexer(u, "gitea_codes") if _, err := indexer.Init(context.Background()); err != nil { - assert.Fail(t, "Unable to init ES indexer Error: %v", err) if indexer != nil { indexer.Close() } - return + assert.FailNow(t, "Unable to init ES indexer Error: %v", err) } defer indexer.Close() diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 0d6a8406d3..e149066494 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -96,8 +96,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m } if len(options.IncludedLabelIDs) == 0 && len(options.IncludedAnyLabelIDs) > 0 { - _ = ctx // issue_model.GetLabelsByIDs should be called with ctx, this line can be removed when it's done. - labels, err := issue_model.GetLabelsByIDs(options.IncludedAnyLabelIDs, "name") + labels, err := issue_model.GetLabelsByIDs(ctx, options.IncludedAnyLabelIDs, "name") if err != nil { return nil, fmt.Errorf("GetLabelsByIDs: %v", err) } diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 020659c82b..ef06d8862a 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -204,12 +204,13 @@ func getIssueIndexerQueueHandler(ctx context.Context) func(items ...*IndexerMeta func populateIssueIndexer(ctx context.Context) { ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: PopulateIssueIndexer", process.SystemProcessType, true) defer finished() - if err := PopulateIssueIndexer(ctx, true); err != nil { + ctx = contextWithKeepRetry(ctx) // keep retrying since it's a background task + if err := PopulateIssueIndexer(ctx); err != nil { log.Error("Issue indexer population failed: %v", err) } } -func PopulateIssueIndexer(ctx context.Context, keepRetrying bool) error { +func PopulateIssueIndexer(ctx context.Context) error { for page := 1; ; page++ { select { case <-ctx.Done(): @@ -232,20 +233,8 @@ func PopulateIssueIndexer(ctx context.Context, keepRetrying bool) error { } for _, repo := range repos { - for { - select { - case <-ctx.Done(): - return fmt.Errorf("shutdown before completion: %w", ctx.Err()) - default: - } - if err := updateRepoIndexer(ctx, repo.ID); err != nil { - if keepRetrying && ctx.Err() == nil { - log.Warn("Retry to populate issue indexer for repo %d: %v", repo.ID, err) - continue - } - return fmt.Errorf("populate issue indexer for repo %d: %v", repo.ID, err) - } - break + if err := updateRepoIndexer(ctx, repo.ID); err != nil { + return fmt.Errorf("populate issue indexer for repo %d: %v", repo.ID, err) } } } @@ -259,8 +248,8 @@ func UpdateRepoIndexer(ctx context.Context, repoID int64) { } // UpdateIssueIndexer add/update an issue to the issue indexer -func UpdateIssueIndexer(issueID int64) { - if err := updateIssueIndexer(issueID); err != nil { +func UpdateIssueIndexer(ctx context.Context, issueID int64) { + if err := updateIssueIndexer(ctx, issueID); err != nil { log.Error("Unable to push issue %d to issue indexer: %v", issueID, err) } } diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index a4e1c899fc..0ec23164c2 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -5,14 +5,13 @@ package issues import ( "context" - "path" - "path/filepath" "testing" - "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/indexer/issues/bleve" + "code.gitea.io/gitea/modules/indexer/issues/internal" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" _ "code.gitea.io/gitea/models" _ "code.gitea.io/gitea/models/actions" @@ -22,71 +21,7 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", "..", ".."), - }) -} - -func TestBleveSearchIssues(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - setting.CfgProvider, _ = setting.NewConfigProviderFromData("") - - tmpIndexerDir := t.TempDir() - - setting.CfgProvider.Section("queue.issue_indexer").Key("DATADIR").MustString(path.Join(tmpIndexerDir, "issues.queue")) - - oldIssuePath := setting.Indexer.IssuePath - setting.Indexer.IssuePath = path.Join(tmpIndexerDir, "issues.queue") - defer func() { - setting.Indexer.IssuePath = oldIssuePath - }() - - setting.Indexer.IssueType = "bleve" - setting.LoadQueueSettings() - InitIssueIndexer(true) - defer func() { - if bleveIndexer, ok := (*globalIndexer.Load()).(*bleve.Indexer); ok { - bleveIndexer.Close() - } - }() - - time.Sleep(5 * time.Second) - - t.Run("issue2", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "issue2", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.EqualValues(t, []int64{2}, ids) - }) - - t.Run("first", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "first", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.EqualValues(t, []int64{1}, ids) - }) - - t.Run("for", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "for", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.ElementsMatch(t, []int64{1, 2, 3, 5, 11}, ids) - }) - - t.Run("good", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "good", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.EqualValues(t, []int64{1}, ids) - }) + unittest.MainTest(m) } func TestDBSearchIssues(t *testing.T) { @@ -95,39 +30,390 @@ func TestDBSearchIssues(t *testing.T) { setting.Indexer.IssueType = "db" InitIssueIndexer(true) - t.Run("issue2", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "issue2", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.EqualValues(t, []int64{2}, ids) - }) - - t.Run("first", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "first", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.EqualValues(t, []int64{1}, ids) - }) - - t.Run("for", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "for", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.ElementsMatch(t, []int64{1, 2, 3, 5, 11}, ids) - }) - - t.Run("good", func(t *testing.T) { - ids, _, err := SearchIssues(context.TODO(), &SearchOptions{ - Keyword: "good", - RepoIDs: []int64{1}, - }) - assert.NoError(t, err) - assert.EqualValues(t, []int64{1}, ids) - }) + t.Run("search issues with keyword", searchIssueWithKeyword) + t.Run("search issues in repo", searchIssueInRepo) + t.Run("search issues by ID", searchIssueByID) + t.Run("search issues is pr", searchIssueIsPull) + t.Run("search issues is closed", searchIssueIsClosed) + t.Run("search issues by milestone", searchIssueByMilestoneID) + t.Run("search issues by label", searchIssueByLabelID) + t.Run("search issues by time", searchIssueByTime) + t.Run("search issues with order", searchIssueWithOrder) + t.Run("search issues in project", searchIssueInProject) + t.Run("search issues with paginator", searchIssueWithPaginator) +} + +func searchIssueWithKeyword(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + Keyword: "issue2", + RepoIDs: []int64{1}, + }, + []int64{2}, + }, + { + SearchOptions{ + Keyword: "first", + RepoIDs: []int64{1}, + }, + []int64{1}, + }, + { + SearchOptions{ + Keyword: "for", + RepoIDs: []int64{1}, + }, + []int64{11, 5, 3, 2, 1}, + }, + { + SearchOptions{ + Keyword: "good", + RepoIDs: []int64{1}, + }, + []int64{1}, + }, + } + + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueInRepo(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + RepoIDs: []int64{1}, + }, + []int64{11, 5, 3, 2, 1}, + }, + { + SearchOptions{ + RepoIDs: []int64{2}, + }, + []int64{7, 4}, + }, + { + SearchOptions{ + RepoIDs: []int64{3}, + }, + []int64{12, 6}, + }, + { + SearchOptions{ + RepoIDs: []int64{4}, + }, + []int64{}, + }, + { + SearchOptions{ + RepoIDs: []int64{5}, + }, + []int64{15}, + }, + } + + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueByID(t *testing.T) { + int64Pointer := func(x int64) *int64 { + return &x + } + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + PosterID: int64Pointer(1), + }, + []int64{11, 6, 3, 2, 1}, + }, + { + SearchOptions{ + AssigneeID: int64Pointer(1), + }, + []int64{6, 1}, + }, + { + SearchOptions{ + MentionID: int64Pointer(4), + }, + []int64{1}, + }, + { + SearchOptions{ + ReviewedID: int64Pointer(1), + }, + []int64{}, + }, + { + SearchOptions{ + ReviewRequestedID: int64Pointer(1), + }, + []int64{12}, + }, + { + SearchOptions{ + SubscriberID: int64Pointer(1), + }, + []int64{11, 6, 5, 3, 2, 1}, + }, + { + // issue 20 request user 15 and team 5 which user 15 belongs to + // the review request number of issue 20 should be 1 + SearchOptions{ + ReviewRequestedID: int64Pointer(15), + }, + []int64{12, 20}, + }, + { + // user 20 approved the issue 20, so return nothing + SearchOptions{ + ReviewRequestedID: int64Pointer(20), + }, + []int64{}, + }, + } + + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueIsPull(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + IsPull: util.OptionalBoolFalse, + }, + []int64{17, 16, 15, 14, 13, 6, 5, 18, 10, 7, 4, 1}, + }, + { + SearchOptions{ + IsPull: util.OptionalBoolTrue, + }, + []int64{12, 11, 20, 19, 9, 8, 3, 2}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueIsClosed(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + IsClosed: util.OptionalBoolFalse, + }, + []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1}, + }, + { + SearchOptions{ + IsClosed: util.OptionalBoolTrue, + }, + []int64{5, 4}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueByMilestoneID(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + MilestoneIDs: []int64{1}, + }, + []int64{2}, + }, + { + SearchOptions{ + MilestoneIDs: []int64{3}, + }, + []int64{3}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueByLabelID(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + IncludedLabelIDs: []int64{1}, + }, + []int64{2, 1}, + }, + { + SearchOptions{ + IncludedLabelIDs: []int64{4}, + }, + []int64{2}, + }, + { + SearchOptions{ + ExcludedLabelIDs: []int64{1}, + }, + []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueByTime(t *testing.T) { + int64Pointer := func(i int64) *int64 { + return &i + } + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + UpdatedAfterUnix: int64Pointer(0), + }, + []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueWithOrder(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + SortBy: internal.SortByCreatedAsc, + }, + []int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueInProject(t *testing.T) { + int64Pointer := func(i int64) *int64 { + return &i + } + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + ProjectID: int64Pointer(1), + }, + []int64{5, 3, 2, 1}, + }, + { + SearchOptions{ + ProjectBoardID: int64Pointer(1), + }, + []int64{1}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + +func searchIssueWithPaginator(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + expectedTotal int64 + }{ + { + SearchOptions{ + Paginator: &db.ListOptions{ + PageSize: 5, + }, + }, + []int64{17, 16, 15, 14, 13}, + 20, + }, + } + for _, test := range tests { + issueIDs, total, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + assert.Equal(t, test.expectedTotal, total) + } } diff --git a/modules/indexer/issues/util.go b/modules/indexer/issues/util.go index 2dec3b71db..510b4060b2 100644 --- a/modules/indexer/issues/util.go +++ b/modules/indexer/issues/util.go @@ -107,7 +107,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD NoLabel: len(labels) == 0, MilestoneID: issue.MilestoneID, ProjectID: projectID, - ProjectBoardID: issue.ProjectBoardID(), + ProjectBoardID: issue.ProjectBoardID(ctx), PosterID: issue.PosterID, AssigneeID: issue.AssigneeID, MentionIDs: mentionIDs, @@ -127,15 +127,15 @@ func updateRepoIndexer(ctx context.Context, repoID int64) error { return fmt.Errorf("issue_model.GetIssueIDsByRepoID: %w", err) } for _, id := range ids { - if err := updateIssueIndexer(id); err != nil { + if err := updateIssueIndexer(ctx, id); err != nil { return err } } return nil } -func updateIssueIndexer(issueID int64) error { - return pushIssueIndexerQueue(&IndexerMetadata{ID: issueID}) +func updateIssueIndexer(ctx context.Context, issueID int64) error { + return pushIssueIndexerQueue(ctx, &IndexerMetadata{ID: issueID}) } func deleteRepoIssueIndexer(ctx context.Context, repoID int64) error { @@ -148,13 +148,21 @@ func deleteRepoIssueIndexer(ctx context.Context, repoID int64) error { if len(ids) == 0 { return nil } - return pushIssueIndexerQueue(&IndexerMetadata{ + return pushIssueIndexerQueue(ctx, &IndexerMetadata{ IDs: ids, IsDelete: true, }) } -func pushIssueIndexerQueue(data *IndexerMetadata) error { +type keepRetryKey struct{} + +// contextWithKeepRetry returns a context with a key indicating that the indexer should keep retrying. +// Please note that it's for background tasks only, and it should not be used for user requests, or it may cause blocking. +func contextWithKeepRetry(ctx context.Context) context.Context { + return context.WithValue(ctx, keepRetryKey{}, true) +} + +func pushIssueIndexerQueue(ctx context.Context, data *IndexerMetadata) error { if issueIndexerQueue == nil { // Some unit tests will trigger indexing, but the queue is not initialized. // It's OK to ignore it, but log a warning message in case it's not a unit test. @@ -162,12 +170,26 @@ func pushIssueIndexerQueue(data *IndexerMetadata) error { return nil } - err := issueIndexerQueue.Push(data) - if errors.Is(err, queue.ErrAlreadyInQueue) { - return nil + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + err := issueIndexerQueue.Push(data) + if errors.Is(err, queue.ErrAlreadyInQueue) { + return nil + } + if errors.Is(err, context.DeadlineExceeded) { // the queue is full + log.Warn("It seems that issue indexer is slow and the queue is full. Please check the issue indexer or increase the queue size.") + if ctx.Value(keepRetryKey{}) == nil { + return err + } + // It will be better to increase the queue size instead of retrying, but users may ignore the previous warning message. + // However, even it retries, it may still cause index loss when there's a deadline in the context. + log.Debug("Retry to push %+v to issue indexer queue", data) + continue + } + return err } - if errors.Is(err, context.DeadlineExceeded) { - log.Warn("It seems that issue indexer is slow and the queue is full. Please check the issue indexer or increase the queue size.") - } - return err } diff --git a/modules/indexer/stats/db.go b/modules/indexer/stats/db.go index 2a0475dea6..163843b47f 100644 --- a/modules/indexer/stats/db.go +++ b/modules/indexer/stats/db.go @@ -68,7 +68,7 @@ func (db *DBIndexer) Index(id int64) error { } return err } - err = repo_model.UpdateLanguageStats(repo, commitID, stats) + err = repo_model.UpdateLanguageStats(ctx, repo, commitID, stats) if err != nil { log.Error("Unable to update language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err) return err diff --git a/modules/indexer/stats/indexer.go b/modules/indexer/stats/indexer.go index 6bfa8bdedb..7ec89e2afb 100644 --- a/modules/indexer/stats/indexer.go +++ b/modules/indexer/stats/indexer.go @@ -4,6 +4,8 @@ package stats import ( + "context" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/graceful" @@ -28,14 +30,14 @@ func Init() error { return err } - go populateRepoIndexer() + go populateRepoIndexer(db.DefaultContext) return nil } // populateRepoIndexer populate the repo indexer with pre-existing data. This // should only be run when the indexer is created for the first time. -func populateRepoIndexer() { +func populateRepoIndexer(ctx context.Context) { log.Info("Populating the repo stats indexer with existing repositories") isShutdown := graceful.GetManager().IsShutdown() @@ -62,7 +64,7 @@ func populateRepoIndexer() { return default: } - ids, err := repo_model.GetUnindexedRepos(repo_model.RepoIndexerTypeStats, maxRepoID, 0, 50) + ids, err := repo_model.GetUnindexedRepos(ctx, repo_model.RepoIndexerTypeStats, maxRepoID, 0, 50) if err != nil { log.Error("populateRepoIndexer: %v", err) return diff --git a/modules/indexer/stats/indexer_test.go b/modules/indexer/stats/indexer_test.go index c031515434..5be45d7a3b 100644 --- a/modules/indexer/stats/indexer_test.go +++ b/modules/indexer/stats/indexer_test.go @@ -5,7 +5,6 @@ package stats import ( "context" - "path/filepath" "testing" "time" @@ -23,9 +22,7 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", "..", ".."), - }) + unittest.MainTest(m) } func TestRepoStatsIndex(t *testing.T) { @@ -48,7 +45,7 @@ func TestRepoStatsIndex(t *testing.T) { status, err := repo_model.GetIndexerStatus(db.DefaultContext, repo, repo_model.RepoIndexerTypeStats) assert.NoError(t, err) assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", status.CommitSha) - langs, err := repo_model.GetTopLanguageStats(repo, 5) + langs, err := repo_model.GetTopLanguageStats(db.DefaultContext, repo, 5) assert.NoError(t, err) assert.Empty(t, langs) } diff --git a/modules/lfs/filesystem_client.go b/modules/lfs/filesystem_client.go index 835551e00c..3503a9effc 100644 --- a/modules/lfs/filesystem_client.go +++ b/modules/lfs/filesystem_client.go @@ -15,7 +15,7 @@ import ( // FilesystemClient is used to read LFS data from a filesystem path type FilesystemClient struct { - lfsdir string + lfsDir string } // BatchSize returns the preferred size of batchs to process @@ -25,16 +25,12 @@ func (c *FilesystemClient) BatchSize() int { func newFilesystemClient(endpoint *url.URL) *FilesystemClient { path, _ := util.FileURLToPath(endpoint) - - lfsdir := filepath.Join(path, "lfs", "objects") - - client := &FilesystemClient{lfsdir} - - return client + lfsDir := filepath.Join(path, "lfs", "objects") + return &FilesystemClient{lfsDir} } func (c *FilesystemClient) objectPath(oid string) string { - return filepath.Join(c.lfsdir, oid[0:2], oid[2:4], oid) + return filepath.Join(c.lfsDir, oid[0:2], oid[2:4], oid) } // Download reads the specific LFS object from the target path diff --git a/modules/lfs/http_client.go b/modules/lfs/http_client.go index ec0d6269bd..de0b1e4fed 100644 --- a/modules/lfs/http_client.go +++ b/modules/lfs/http_client.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "io" "net/http" "net/url" "strings" @@ -17,7 +18,7 @@ import ( "code.gitea.io/gitea/modules/proxy" ) -const batchSize = 20 +const httpBatchSize = 20 // HTTPClient is used to communicate with the LFS server // https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md @@ -29,7 +30,7 @@ type HTTPClient struct { // BatchSize returns the preferred size of batchs to process func (c *HTTPClient) BatchSize() int { - return batchSize + return httpBatchSize } func newHTTPClient(endpoint *url.URL, httpTransport *http.Transport) *HTTPClient { @@ -43,28 +44,25 @@ func newHTTPClient(endpoint *url.URL, httpTransport *http.Transport) *HTTPClient Transport: httpTransport, } - client := &HTTPClient{ - client: hc, - endpoint: strings.TrimSuffix(endpoint.String(), "/"), - transfers: make(map[string]TransferAdapter), - } - basic := &BasicTransferAdapter{hc} - - client.transfers[basic.Name()] = basic + client := &HTTPClient{ + client: hc, + endpoint: strings.TrimSuffix(endpoint.String(), "/"), + transfers: map[string]TransferAdapter{ + basic.Name(): basic, + }, + } return client } func (c *HTTPClient) transferNames() []string { keys := make([]string, len(c.transfers)) - i := 0 for k := range c.transfers { keys[i] = k i++ } - return keys } @@ -74,7 +72,6 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin url := fmt.Sprintf("%s/objects/batch", c.endpoint) request := &BatchRequest{operation, c.transferNames(), nil, objects} - payload := new(bytes.Buffer) err := json.NewEncoder(payload).Encode(request) if err != nil { @@ -82,32 +79,17 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin return nil, err } - log.Trace("Calling: %s", url) - - req, err := http.NewRequestWithContext(ctx, "POST", url, payload) + req, err := createRequest(ctx, http.MethodPost, url, map[string]string{"Content-Type": MediaType}, payload) if err != nil { - log.Error("Error creating request: %v", err) return nil, err } - req.Header.Set("Content-type", MediaType) - req.Header.Set("Accept", MediaType) - res, err := c.client.Do(req) + res, err := performRequest(ctx, c.client, req) if err != nil { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - log.Error("Error while processing request: %v", err) return nil, err } defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unexpected server response: %s", res.Status) - } - var response BatchResponse err = json.NewDecoder(res.Body).Decode(&response) if err != nil { @@ -177,7 +159,7 @@ func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc link, ok := object.Actions["upload"] if !ok { log.Debug("%+v", object) - return errors.New("Missing action 'upload'") + return errors.New("missing action 'upload'") } content, err := uc(object.Pointer, nil) @@ -187,8 +169,6 @@ func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc err = transferAdapter.Upload(ctx, link, object.Pointer, content) - content.Close() - if err != nil { return err } @@ -203,7 +183,7 @@ func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc link, ok := object.Actions["download"] if !ok { log.Debug("%+v", object) - return errors.New("Missing action 'download'") + return errors.New("missing action 'download'") } content, err := transferAdapter.Download(ctx, link) @@ -219,3 +199,59 @@ func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc return nil } + +// createRequest creates a new request, and sets the headers. +func createRequest(ctx context.Context, method, url string, headers map[string]string, body io.Reader) (*http.Request, error) { + log.Trace("createRequest: %s", url) + req, err := http.NewRequestWithContext(ctx, method, url, body) + if err != nil { + log.Error("Error creating request: %v", err) + return nil, err + } + + for key, value := range headers { + req.Header.Set(key, value) + } + req.Header.Set("Accept", MediaType) + + return req, nil +} + +// performRequest sends a request, optionally performs a callback on the request and returns the response. +// If the status code is 200, the response is returned, and it will contain a non-nil Body. +// Otherwise, it will return an error, and the Body will be nil or closed. +func performRequest(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { + log.Trace("performRequest: %s", req.URL) + res, err := client.Do(req) + if err != nil { + select { + case <-ctx.Done(): + return res, ctx.Err() + default: + } + log.Error("Error while processing request: %v", err) + return res, err + } + + if res.StatusCode != http.StatusOK { + defer res.Body.Close() + return res, handleErrorResponse(res) + } + + return res, nil +} + +func handleErrorResponse(resp *http.Response) error { + var er ErrorResponse + err := json.NewDecoder(resp.Body).Decode(&er) + if err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF + } + log.Error("Error decoding json: %v", err) + return err + } + + log.Trace("ErrorResponse: %v", er) + return errors.New(er.Message) +} diff --git a/modules/lfs/http_client_test.go b/modules/lfs/http_client_test.go index cb71b9008a..7459d9c0c9 100644 --- a/modules/lfs/http_client_test.go +++ b/modules/lfs/http_client_test.go @@ -177,7 +177,7 @@ func TestHTTPClientDownload(t *testing.T) { // case 0 { endpoint: "https://status-not-ok.io", - expectederror: "Unexpected server response: ", + expectederror: io.ErrUnexpectedEOF.Error(), }, // case 1 { @@ -207,7 +207,7 @@ func TestHTTPClientDownload(t *testing.T) { // case 6 { endpoint: "https://empty-actions-map.io", - expectederror: "Missing action 'download'", + expectederror: "missing action 'download'", }, // case 7 { @@ -217,27 +217,28 @@ func TestHTTPClientDownload(t *testing.T) { // case 8 { endpoint: "https://upload-actions-map.io", - expectederror: "Missing action 'download'", + expectederror: "missing action 'download'", }, // case 9 { endpoint: "https://verify-actions-map.io", - expectederror: "Missing action 'download'", + expectederror: "missing action 'download'", }, // case 10 { endpoint: "https://unknown-actions-map.io", - expectederror: "Missing action 'download'", + expectederror: "missing action 'download'", }, } for n, c := range cases { client := &HTTPClient{ - client: hc, - endpoint: c.endpoint, - transfers: make(map[string]TransferAdapter), + client: hc, + endpoint: c.endpoint, + transfers: map[string]TransferAdapter{ + "dummy": dummy, + }, } - client.transfers["dummy"] = dummy err := client.Download(context.Background(), []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error { if objectError != nil { @@ -284,7 +285,7 @@ func TestHTTPClientUpload(t *testing.T) { // case 0 { endpoint: "https://status-not-ok.io", - expectederror: "Unexpected server response: ", + expectederror: io.ErrUnexpectedEOF.Error(), }, // case 1 { @@ -319,7 +320,7 @@ func TestHTTPClientUpload(t *testing.T) { // case 7 { endpoint: "https://download-actions-map.io", - expectederror: "Missing action 'upload'", + expectederror: "missing action 'upload'", }, // case 8 { @@ -329,22 +330,23 @@ func TestHTTPClientUpload(t *testing.T) { // case 9 { endpoint: "https://verify-actions-map.io", - expectederror: "Missing action 'upload'", + expectederror: "missing action 'upload'", }, // case 10 { endpoint: "https://unknown-actions-map.io", - expectederror: "Missing action 'upload'", + expectederror: "missing action 'upload'", }, } for n, c := range cases { client := &HTTPClient{ - client: hc, - endpoint: c.endpoint, - transfers: make(map[string]TransferAdapter), + client: hc, + endpoint: c.endpoint, + transfers: map[string]TransferAdapter{ + "dummy": dummy, + }, } - client.transfers["dummy"] = dummy err := client.Upload(context.Background(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) { return io.NopCloser(new(bytes.Buffer)), objectError diff --git a/modules/lfs/pointer.go b/modules/lfs/pointer.go index d7653e836c..3e5bb8f91d 100644 --- a/modules/lfs/pointer.go +++ b/modules/lfs/pointer.go @@ -29,10 +29,10 @@ const ( var ( // ErrMissingPrefix occurs if the content lacks the LFS prefix - ErrMissingPrefix = errors.New("Content lacks the LFS prefix") + ErrMissingPrefix = errors.New("content lacks the LFS prefix") // ErrInvalidStructure occurs if the content has an invalid structure - ErrInvalidStructure = errors.New("Content has an invalid structure") + ErrInvalidStructure = errors.New("content has an invalid structure") // ErrInvalidOIDFormat occurs if the oid has an invalid format ErrInvalidOIDFormat = errors.New("OID has an invalid format") diff --git a/modules/lfs/transferadapter.go b/modules/lfs/transferadapter.go index 649497aabb..d425b91946 100644 --- a/modules/lfs/transferadapter.go +++ b/modules/lfs/transferadapter.go @@ -6,8 +6,6 @@ package lfs import ( "bytes" "context" - "errors" - "fmt" "io" "net/http" @@ -15,7 +13,7 @@ import ( "code.gitea.io/gitea/modules/log" ) -// TransferAdapter represents an adapter for downloading/uploading LFS objects +// TransferAdapter represents an adapter for downloading/uploading LFS objects. type TransferAdapter interface { Name() string Download(ctx context.Context, l *Link) (io.ReadCloser, error) @@ -23,41 +21,48 @@ type TransferAdapter interface { Verify(ctx context.Context, l *Link, p Pointer) error } -// BasicTransferAdapter implements the "basic" adapter +// BasicTransferAdapter implements the "basic" adapter. type BasicTransferAdapter struct { client *http.Client } -// Name returns the name of the adapter +// Name returns the name of the adapter. func (a *BasicTransferAdapter) Name() string { return "basic" } -// Download reads the download location and downloads the data +// Download reads the download location and downloads the data. func (a *BasicTransferAdapter) Download(ctx context.Context, l *Link) (io.ReadCloser, error) { - resp, err := a.performRequest(ctx, "GET", l, nil, nil) + req, err := createRequest(ctx, http.MethodGet, l.Href, l.Header, nil) + if err != nil { + return nil, err + } + resp, err := performRequest(ctx, a.client, req) if err != nil { return nil, err } return resp.Body, nil } -// Upload sends the content to the LFS server +// Upload sends the content to the LFS server. func (a *BasicTransferAdapter) Upload(ctx context.Context, l *Link, p Pointer, r io.Reader) error { - _, err := a.performRequest(ctx, "PUT", l, r, func(req *http.Request) { - if len(req.Header.Get("Content-Type")) == 0 { - req.Header.Set("Content-Type", "application/octet-stream") - } - - if req.Header.Get("Transfer-Encoding") == "chunked" { - req.TransferEncoding = []string{"chunked"} - } - - req.ContentLength = p.Size - }) + req, err := createRequest(ctx, http.MethodPut, l.Href, l.Header, r) if err != nil { return err } + if req.Header.Get("Content-Type") == "" { + req.Header.Set("Content-Type", "application/octet-stream") + } + if req.Header.Get("Transfer-Encoding") == "chunked" { + req.TransferEncoding = []string{"chunked"} + } + req.ContentLength = p.Size + + res, err := performRequest(ctx, a.client, req) + if err != nil { + return err + } + defer res.Body.Close() return nil } @@ -69,66 +74,15 @@ func (a *BasicTransferAdapter) Verify(ctx context.Context, l *Link, p Pointer) e return err } - _, err = a.performRequest(ctx, "POST", l, bytes.NewReader(b), func(req *http.Request) { - req.Header.Set("Content-Type", MediaType) - }) + req, err := createRequest(ctx, http.MethodPost, l.Href, l.Header, bytes.NewReader(b)) if err != nil { return err } + req.Header.Set("Content-Type", MediaType) + res, err := performRequest(ctx, a.client, req) + if err != nil { + return err + } + defer res.Body.Close() return nil } - -func (a *BasicTransferAdapter) performRequest(ctx context.Context, method string, l *Link, body io.Reader, callback func(*http.Request)) (*http.Response, error) { - log.Trace("Calling: %s %s", method, l.Href) - - req, err := http.NewRequestWithContext(ctx, method, l.Href, body) - if err != nil { - log.Error("Error creating request: %v", err) - return nil, err - } - for key, value := range l.Header { - req.Header.Set(key, value) - } - req.Header.Set("Accept", MediaType) - - if callback != nil { - callback(req) - } - - res, err := a.client.Do(req) - if err != nil { - select { - case <-ctx.Done(): - return res, ctx.Err() - default: - } - log.Error("Error while processing request: %v", err) - return res, err - } - - if res.StatusCode != http.StatusOK { - return res, handleErrorResponse(res) - } - - return res, nil -} - -func handleErrorResponse(resp *http.Response) error { - defer resp.Body.Close() - - er, err := decodeResponseError(resp.Body) - if err != nil { - return fmt.Errorf("Request failed with status %s", resp.Status) - } - log.Trace("ErrorRespone: %v", er) - return errors.New(er.Message) -} - -func decodeResponseError(r io.Reader) (ErrorResponse, error) { - var er ErrorResponse - err := json.NewDecoder(r).Decode(&er) - if err != nil { - log.Error("Error decoding json: %v", err) - } - return er, err -} diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index a6dac12039..9b6175649f 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -153,18 +153,31 @@ func (r *Writer) WriteRegularLink(l org.RegularLink) { link = []byte(util.URLJoin(r.URLPrefix, lnk)) } - description := string(link) - if l.Description != nil { - description = r.WriteNodesAsString(l.Description...) - } + // Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427 switch l.Kind() { case "image": - imageSrc := getMediaURL(link) - fmt.Fprintf(r, `%s`, imageSrc, description, description) + if l.Description == nil { + imageSrc := getMediaURL(link) + fmt.Fprintf(r, `%s`, imageSrc, link, link) + } else { + description := strings.TrimPrefix(org.String(l.Description...), "file:") + imageSrc := getMediaURL([]byte(description)) + fmt.Fprintf(r, `%s`, link, imageSrc, imageSrc) + } case "video": - videoSrc := getMediaURL(link) - fmt.Fprintf(r, ``, videoSrc, description, description) + if l.Description == nil { + imageSrc := getMediaURL(link) + fmt.Fprintf(r, ``, imageSrc, link, link) + } else { + description := strings.TrimPrefix(org.String(l.Description...), "file:") + videoSrc := getMediaURL([]byte(description)) + fmt.Fprintf(r, ``, link, videoSrc, videoSrc) + } default: + description := string(link) + if l.Description != nil { + description = r.WriteNodesAsString(l.Description...) + } fmt.Fprintf(r, `%s`, link, description, description) } } diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index d6467c36f7..8f454e9955 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -42,7 +42,7 @@ func TestRender_StandardLinks(t *testing.T) { "

WikiPage

") } -func TestRender_Images(t *testing.T) { +func TestRender_Media(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL @@ -60,6 +60,18 @@ func TestRender_Images(t *testing.T) { test("[[file:"+url+"]]", "

\""+result+"\"

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

https://example.com/example.svg

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

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

https://example.com/example.svg

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

`) } func TestRender_Source(t *testing.T) { diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go index 33678256c3..1bf8f58b93 100755 --- a/modules/metrics/collector.go +++ b/modules/metrics/collector.go @@ -7,6 +7,7 @@ import ( "runtime" activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/prometheus/client_golang/prometheus" @@ -232,7 +233,7 @@ func (c Collector) Describe(ch chan<- *prometheus.Desc) { // Collect returns the metrics with values func (c Collector) Collect(ch chan<- prometheus.Metric) { - stats := activities_model.GetStatistic() + stats := activities_model.GetStatistic(db.DefaultContext) ch <- prometheus.MustNewConstMetric( c.Accesses, diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go index 2e2e35d24a..36b2a912ea 100644 --- a/modules/process/manager_test.go +++ b/modules/process/manager_test.go @@ -50,7 +50,7 @@ func TestManager_Cancel(t *testing.T) { select { case <-ctx.Done(): default: - assert.Fail(t, "Cancel should cancel the provided context") + assert.FailNow(t, "Cancel should cancel the provided context") } finished() @@ -62,7 +62,7 @@ func TestManager_Cancel(t *testing.T) { select { case <-ctx.Done(): default: - assert.Fail(t, "Cancel should cancel the provided context") + assert.FailNow(t, "Cancel should cancel the provided context") } finished() } diff --git a/modules/queue/manager_test.go b/modules/queue/manager_test.go index 9a5b616b1a..15dd1b4f2f 100644 --- a/modules/queue/manager_test.go +++ b/modules/queue/manager_test.go @@ -46,7 +46,7 @@ CONN_STR = redis:// assert.Equal(t, "default", q.GetName()) assert.Equal(t, "level", q.GetType()) assert.Equal(t, filepath.Join(setting.AppDataPath, "queues/common"), q.baseConfig.DataFullDir) - assert.Equal(t, 100, q.baseConfig.Length) + assert.Equal(t, 100000, q.baseConfig.Length) assert.Equal(t, 20, q.batchLength) assert.Equal(t, "", q.baseConfig.ConnStr) assert.Equal(t, "default_queue", q.baseConfig.QueueFullName) diff --git a/modules/queue/queue.go b/modules/queue/queue.go index 1ad7320e31..577fd4d498 100644 --- a/modules/queue/queue.go +++ b/modules/queue/queue.go @@ -9,6 +9,8 @@ // - An item can be a simple value, such as an integer, or a more complex structure that has multiple fields. // Usually a item serves as a task or a message. Sets of items will be sent to a queue handler to be processed. // - It's represented as a JSON-marshaled binary slice in the queue +// - Since the item is marshaled by JSON, and JSON doesn't have stable key-order/type support, +// so the decoded handler item may not be the same as the original "pushed" one if you use map/any types, // // 2. Batch: // - A collection of items that are grouped together for processing. Each worker receives a batch of items. diff --git a/modules/references/references_test.go b/modules/references/references_test.go index 75e002c99a..b60d6b0459 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -78,15 +78,15 @@ func TestFindAllIssueReferences(t *testing.T) { []testResult{}, }, { - "This user3/repo4#200 yes.", + "This org3/repo4#200 yes.", []testResult{ - {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, + {200, "org3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 19}, nil, ""}, }, }, { - "This user3/repo4!200 yes.", + "This org3/repo4!200 yes.", []testResult{ - {200, "user3", "repo4", "200", true, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, + {200, "org3", "repo4", "200", true, XRefActionNone, &RefSpan{Start: 5, End: 19}, nil, ""}, }, }, { @@ -106,13 +106,13 @@ func TestFindAllIssueReferences(t *testing.T) { }, }, { - "This [four](http://gitea.com:3000/user3/repo4/issues/203) yes.", + "This [four](http://gitea.com:3000/org3/repo4/issues/203) yes.", []testResult{ - {203, "user3", "repo4", "203", false, XRefActionNone, nil, nil, ""}, + {203, "org3", "repo4", "203", false, XRefActionNone, nil, nil, ""}, }, }, { - "This [five](http://github.com/user3/repo4/issues/204) no.", + "This [five](http://github.com/org3/repo4/issues/204) no.", []testResult{}, }, { @@ -151,9 +151,9 @@ func TestFindAllIssueReferences(t *testing.T) { }, }, { - "Do you fix user6/repo6#300 ? yes", + "Do you fix org6/repo6#300 ? yes", []testResult{ - {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 11, End: 26}, &RefSpan{Start: 7, End: 10}, ""}, + {300, "org6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 11, End: 25}, &RefSpan{Start: 7, End: 10}, ""}, }, }, { @@ -190,9 +190,9 @@ func TestFindAllIssueReferences(t *testing.T) { }, }, { - "This user3/repo4#200, yes.", + "This org3/repo4#200, yes.", []testResult{ - {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, + {200, "org3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 19}, nil, ""}, }, }, { @@ -498,15 +498,15 @@ func TestCustomizeCloseKeywords(t *testing.T) { }, }, { - "Cerró user6/repo6#300 yes", + "Cerró org6/repo6#300 yes", []testResult{ - {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}, ""}, + {300, "org6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 7, End: 21}, &RefSpan{Start: 0, End: 6}, ""}, }, }, { - "Reabre user3/repo4#200 yes", + "Reabre org3/repo4#200 yes", []testResult{ - {200, "user3", "repo4", "200", false, XRefActionReopens, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}, ""}, + {200, "org3", "repo4", "200", false, XRefActionReopens, &RefSpan{Start: 7, End: 21}, &RefSpan{Start: 0, End: 6}, ""}, }, }, } diff --git a/modules/repository/collaborator_test.go b/modules/repository/collaborator_test.go index c772806819..622f6abce4 100644 --- a/modules/repository/collaborator_test.go +++ b/modules/repository/collaborator_test.go @@ -244,7 +244,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { // update team information and then check permission team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - err = organization.UpdateTeamUnits(team, nil) + err = organization.UpdateTeamUnits(db.DefaultContext, team, nil) assert.NoError(t, err) perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner) assert.NoError(t, err) diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index 0931092597..827b2a9849 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" @@ -103,12 +102,6 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified) } -func initGravatarSource(t *testing.T) { - setting.GravatarSource = "https://secure.gravatar.com/avatar" - err := system_model.Init(db.DefaultContext) - assert.NoError(t, err) -} - func TestPushCommits_AvatarLink(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) @@ -132,7 +125,7 @@ func TestPushCommits_AvatarLink(t *testing.T) { }, } - initGravatarSource(t) + setting.GravatarSource = "https://secure.gravatar.com/avatar" assert.Equal(t, "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor), diff --git a/modules/repository/delete.go b/modules/repository/delete.go index 72c0dc9135..04af98beef 100644 --- a/modules/repository/delete.go +++ b/modules/repository/delete.go @@ -4,24 +4,25 @@ package repository import ( - "code.gitea.io/gitea/models/db" + "context" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" ) // CanUserDelete returns true if user could delete the repository -func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) { +func CanUserDelete(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) { if user.IsAdmin || user.ID == repo.OwnerID { return true, nil } - if err := repo.LoadOwner(db.DefaultContext); err != nil { + if err := repo.LoadOwner(ctx); err != nil { return false, err } if repo.Owner.IsOrganization() { - isAdmin, err := organization.OrgFromUser(repo.Owner).IsOrgAdmin(user.ID) + isAdmin, err := organization.OrgFromUser(repo.Owner).IsOrgAdmin(ctx, user.ID) if err != nil { return false, err } diff --git a/modules/repository/fork.go b/modules/repository/fork.go index 8e9f3a8976..fbf0008716 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -4,25 +4,27 @@ package repository import ( + "context" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" ) // CanUserForkRepo returns true if specified user can fork repository. -func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, error) { +func CanUserForkRepo(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (bool, error) { if user == nil { return false, nil } - if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { + if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(ctx, user.ID, repo.ID) { return true, nil } - ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(user.ID) + ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, user.ID) if err != nil { return false, err } for _, org := range ownedOrgs { - if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) { + if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(ctx, org.ID, repo.ID) { return true, nil } } diff --git a/modules/repository/main_test.go b/modules/repository/main_test.go index abaae69866..f81dfcdafb 100644 --- a/modules/repository/main_test.go +++ b/modules/repository/main_test.go @@ -4,7 +4,6 @@ package repository import ( - "path/filepath" "testing" "code.gitea.io/gitea/models/unittest" @@ -13,7 +12,5 @@ import ( ) func TestMain(m *testing.M) { - unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - }) + unittest.MainTest(m) } diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 6bf88e7752..974449112f 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -159,7 +159,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, // note: this will greatly improve release (tag) sync // for pull-mirrors with many tags repo.IsMirror = opts.Mirror - if err = SyncReleasesWithTags(repo, gitRepo); err != nil { + if err = SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { log.Error("Failed to synchronize tags to releases for repository: %v", err) } } @@ -180,12 +180,17 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, defer committer.Close() if opts.Mirror { + remoteAddress, err := util.SanitizeURL(opts.CloneAddr) + if err != nil { + return repo, err + } mirrorModel := repo_model.Mirror{ RepoID: repo.ID, Interval: setting.Mirror.DefaultInterval, EnablePrune: true, NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval), LFS: opts.LFS, + RemoteAddress: remoteAddress, } if opts.LFS { mirrorModel.LFSEndpoint = opts.LFSEndpoint @@ -280,13 +285,13 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo } // SyncReleasesWithTags synchronizes release table with repository tags -func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) error { +func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error { log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) // optimized procedure for pull-mirrors which saves a lot of time (in // particular for repos with many tags). if repo.IsMirror { - return pullMirrorReleaseSync(repo, gitRepo) + return pullMirrorReleaseSync(ctx, repo, gitRepo) } existingRelTags := make(container.Set[string]) @@ -313,7 +318,7 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) return fmt.Errorf("unable to GetTagCommitID for %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err) } if git.IsErrNotExist(err) || commitID != rel.Sha1 { - if err := repo_model.PushUpdateDeleteTag(repo, rel.TagName); err != nil { + if err := repo_model.PushUpdateDeleteTag(ctx, repo, rel.TagName); err != nil { return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err) } } else { @@ -328,7 +333,7 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) return nil } - if err := PushUpdateAddTag(db.DefaultContext, repo, gitRepo, tagName, sha1, refname); err != nil { + if err := PushUpdateAddTag(ctx, repo, gitRepo, tagName, sha1, refname); err != nil { return fmt.Errorf("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %w", tagName, repo.ID, repo.OwnerName, repo.Name, err) } @@ -385,7 +390,7 @@ func PushUpdateAddTag(ctx context.Context, repo *repo_model.Repository, gitRepo rel.PublisherID = author.ID } - return repo_model.SaveOrUpdateTag(repo, &rel) + return repo_model.SaveOrUpdateTag(ctx, repo, &rel) } // StoreMissingLfsObjectsInRepository downloads missing LFS objects @@ -492,13 +497,13 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re // upstream. Hence, after each sync we want the pull-mirror release set to be // identical to the upstream tag set. This is much more efficient for // repositories like https://github.com/vim/vim (with over 13000 tags). -func pullMirrorReleaseSync(repo *repo_model.Repository, gitRepo *git.Repository) error { +func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error { log.Trace("pullMirrorReleaseSync: rebuilding releases for pull-mirror Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) tags, numTags, err := gitRepo.GetTagInfos(0, 0) if err != nil { return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) } - err = db.WithTx(db.DefaultContext, func(ctx context.Context) error { + err = db.WithTx(ctx, func(ctx context.Context) error { // // clear out existing releases // diff --git a/modules/session/db.go b/modules/session/db.go index f86f7d1e9c..9909f2dc1e 100644 --- a/modules/session/db.go +++ b/modules/session/db.go @@ -8,6 +8,7 @@ import ( "sync" "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "gitea.com/go-chi/session" @@ -71,7 +72,7 @@ func (s *DBStore) Release() error { return err } - return auth.UpdateSession(s.sid, data) + return auth.UpdateSession(db.DefaultContext, s.sid, data) } // Flush deletes all session data. @@ -97,7 +98,7 @@ func (p *DBProvider) Init(maxLifetime int64, connStr string) error { // Read returns raw session store by session ID. func (p *DBProvider) Read(sid string) (session.RawStore, error) { - s, err := auth.ReadSession(sid) + s, err := auth.ReadSession(db.DefaultContext, sid) if err != nil { return nil, err } @@ -117,7 +118,7 @@ func (p *DBProvider) Read(sid string) (session.RawStore, error) { // Exist returns true if session with given ID exists. func (p *DBProvider) Exist(sid string) bool { - has, err := auth.ExistSession(sid) + has, err := auth.ExistSession(db.DefaultContext, sid) if err != nil { panic("session/DB: error checking existence: " + err.Error()) } @@ -126,12 +127,12 @@ func (p *DBProvider) Exist(sid string) bool { // Destroy deletes a session by session ID. func (p *DBProvider) Destroy(sid string) error { - return auth.DestroySession(sid) + return auth.DestroySession(db.DefaultContext, sid) } // Regenerate regenerates a session store from old session ID to new one. func (p *DBProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { - s, err := auth.RegenerateSession(oldsid, sid) + s, err := auth.RegenerateSession(db.DefaultContext, oldsid, sid) if err != nil { return nil, err } @@ -151,7 +152,7 @@ func (p *DBProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err err // Count counts and returns number of sessions. func (p *DBProvider) Count() int { - total, err := auth.CountSessions() + total, err := auth.CountSessions(db.DefaultContext) if err != nil { panic("session/DB: error counting records: " + err.Error()) } @@ -160,7 +161,7 @@ func (p *DBProvider) Count() int { // GC calls GC to clean expired sessions. func (p *DBProvider) GC() { - if err := auth.CleanupSessions(p.maxLifetime); err != nil { + if err := auth.CleanupSessions(db.DefaultContext, p.maxLifetime); err != nil { log.Printf("session/DB: error garbage collecting: %v", err) } } diff --git a/modules/setting/actions.go b/modules/setting/actions.go index bfc502c0cb..026bab4bfc 100644 --- a/modules/setting/actions.go +++ b/modules/setting/actions.go @@ -6,6 +6,7 @@ package setting import ( "fmt" "strings" + "time" "code.gitea.io/gitea/modules/log" ) @@ -18,8 +19,11 @@ var ( ArtifactRetentionDays int64 `ini:"ARTIFACT_RETENTION_DAYS"` Enabled bool DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"` + ZombieTaskTimeout time.Duration `ini:"ZOMBIE_TASK_TIMEOUT"` + EndlessTaskTimeout time.Duration `ini:"ENDLESS_TASK_TIMEOUT"` + AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"` }{ - Enabled: false, + Enabled: true, DefaultActionsURL: defaultActionsURLGitHub, } ) @@ -82,5 +86,9 @@ func loadActionsFrom(rootCfg ConfigProvider) error { Actions.ArtifactRetentionDays = 90 } + Actions.ZombieTaskTimeout = sec.Key("ZOMBIE_TASK_TIMEOUT").MustDuration(10 * time.Minute) + Actions.EndlessTaskTimeout = sec.Key("ENDLESS_TASK_TIMEOUT").MustDuration(3 * time.Hour) + Actions.AbandonedJobTimeout = sec.Key("ABANDONED_JOB_TIMEOUT").MustDuration(24 * time.Hour) + return err } diff --git a/modules/setting/config.go b/modules/setting/config.go new file mode 100644 index 0000000000..db189f44ac --- /dev/null +++ b/modules/setting/config.go @@ -0,0 +1,55 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "sync" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting/config" +) + +type PictureStruct struct { + DisableGravatar *config.Value[bool] + EnableFederatedAvatar *config.Value[bool] +} + +type ConfigStruct struct { + Picture *PictureStruct +} + +var ( + defaultConfig *ConfigStruct + defaultConfigOnce sync.Once +) + +func initDefaultConfig() { + config.SetCfgSecKeyGetter(&cfgSecKeyGetter{}) + defaultConfig = &ConfigStruct{ + Picture: &PictureStruct{ + DisableGravatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}, "picture.disable_gravatar"), + EnableFederatedAvatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}, "picture.enable_federated_avatar"), + }, + } +} + +func Config() *ConfigStruct { + defaultConfigOnce.Do(initDefaultConfig) + return defaultConfig +} + +type cfgSecKeyGetter struct{} + +func (c cfgSecKeyGetter) GetValue(sec, key string) (v string, has bool) { + cfgSec, err := CfgProvider.GetSection(sec) + if err != nil { + log.Error("Unable to get config section: %q", sec) + return "", false + } + cfgKey := ConfigSectionKey(cfgSec, key) + if cfgKey == nil { + return "", false + } + return cfgKey.Value(), true +} diff --git a/modules/setting/config/getter.go b/modules/setting/config/getter.go new file mode 100644 index 0000000000..99f9a4775a --- /dev/null +++ b/modules/setting/config/getter.go @@ -0,0 +1,49 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package config + +import ( + "context" + "sync" +) + +var getterMu sync.RWMutex + +type CfgSecKeyGetter interface { + GetValue(sec, key string) (v string, has bool) +} + +var cfgSecKeyGetterInternal CfgSecKeyGetter + +func SetCfgSecKeyGetter(p CfgSecKeyGetter) { + getterMu.Lock() + cfgSecKeyGetterInternal = p + getterMu.Unlock() +} + +func GetCfgSecKeyGetter() CfgSecKeyGetter { + getterMu.RLock() + defer getterMu.RUnlock() + return cfgSecKeyGetterInternal +} + +type DynKeyGetter interface { + GetValue(ctx context.Context, key string) (v string, has bool) + GetRevision(ctx context.Context) int + InvalidateCache() +} + +var dynKeyGetterInternal DynKeyGetter + +func SetDynGetter(p DynKeyGetter) { + getterMu.Lock() + dynKeyGetterInternal = p + getterMu.Unlock() +} + +func GetDynGetter() DynKeyGetter { + getterMu.RLock() + defer getterMu.RUnlock() + return dynKeyGetterInternal +} diff --git a/modules/setting/config/value.go b/modules/setting/config/value.go new file mode 100644 index 0000000000..817fcdb786 --- /dev/null +++ b/modules/setting/config/value.go @@ -0,0 +1,81 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package config + +import ( + "context" + "strconv" + "sync" +) + +type CfgSecKey struct { + Sec, Key string +} + +type Value[T any] struct { + mu sync.RWMutex + + cfgSecKey CfgSecKey + dynKey string + + def, value T + revision int +} + +func (value *Value[T]) parse(s string) (v T) { + switch any(v).(type) { + case bool: + b, _ := strconv.ParseBool(s) + return any(b).(T) + default: + panic("unsupported config type, please complete the code") + } +} + +func (value *Value[T]) Value(ctx context.Context) (v T) { + dg := GetDynGetter() + if dg == nil { + // this is an edge case: the database is not initialized but the system setting is going to be used + // it should panic to avoid inconsistent config values (from config / system setting) and fix the code + panic("no config dyn value getter") + } + + rev := dg.GetRevision(ctx) + + // if the revision in database doesn't change, use the last value + value.mu.RLock() + if rev == value.revision { + v = value.value + value.mu.RUnlock() + return v + } + value.mu.RUnlock() + + // try to parse the config and cache it + var valStr *string + if dynVal, has := dg.GetValue(ctx, value.dynKey); has { + valStr = &dynVal + } else if cfgVal, has := GetCfgSecKeyGetter().GetValue(value.cfgSecKey.Sec, value.cfgSecKey.Key); has { + valStr = &cfgVal + } + if valStr == nil { + v = value.def + } else { + v = value.parse(*valStr) + } + + value.mu.Lock() + value.value = v + value.revision = rev + value.mu.Unlock() + return v +} + +func (value *Value[T]) DynKey() string { + return value.dynKey +} + +func Bool(def bool, cfgSecKey CfgSecKey, dynKey string) *Value[bool] { + return &Value[bool]{def: def, cfgSecKey: cfgSecKey, dynKey: dynKey} +} diff --git a/modules/setting/config_env.go b/modules/setting/config_env.go index b30e44de30..242f40914a 100644 --- a/modules/setting/config_env.go +++ b/modules/setting/config_env.go @@ -149,8 +149,9 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) { continue } } - key := section.Key(keyName) + key := ConfigSectionKey(section, keyName) if key == nil { + changed = true key, err = section.NewKey(keyName, keyValue) if err != nil { log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, keyValue, err) diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go index e14d5ecb41..7d07c479a1 100644 --- a/modules/setting/config_env_test.go +++ b/modules/setting/config_env_test.go @@ -115,3 +115,29 @@ key = old EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile}) assert.Equal(t, "value-from-file\n", cfg.Section("sec").Key("key").String()) } + +func TestEnvironmentToConfigSubSecKey(t *testing.T) { + // the INI package has a quirk: by default, the keys are inherited. + // when maintaining the keys, the newly added sub key should not be affected by the parent key. + cfg, err := NewConfigProviderFromData(` +[sec] +key = some +`) + assert.NoError(t, err) + + changed := EnvironmentToConfig(cfg, []string{"GITEA__sec_0X2E_sub__key=some"}) + assert.True(t, changed) + + tmpFile := t.TempDir() + "/test-sub-sec-key.ini" + defer os.Remove(tmpFile) + err = cfg.SaveTo(tmpFile) + assert.NoError(t, err) + bs, err := os.ReadFile(tmpFile) + assert.NoError(t, err) + assert.Equal(t, `[sec] +key = some + +[sec.sub] +key = some +`, string(bs)) +} diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 8d64286288..132f4acea1 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -213,11 +213,9 @@ func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvi } } - if len(extraConfigs) > 0 { - for _, s := range extraConfigs { - if err := cfg.Append([]byte(s)); err != nil { - return nil, fmt.Errorf("unable to append more config: %v", err) - } + for _, s := range extraConfigs { + if err := cfg.Append([]byte(s)); err != nil { + return nil, fmt.Errorf("unable to append more config: %v", err) } } diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index ab82393106..aea76b989c 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -110,7 +110,7 @@ var OAuth2 = struct { JWTSigningAlgorithm: "RS256", JWTSigningPrivateKeyFile: "jwt/private.pem", MaxTokenLength: math.MaxInt16, - DefaultApplications: []string{"git-credential-oauth", "git-credential-manager"}, + DefaultApplications: []string{"git-credential-oauth", "git-credential-manager", "tea"}, } func loadOAuth2From(rootCfg ConfigProvider) { diff --git a/modules/setting/queue.go b/modules/setting/queue.go index fc15bd07ed..251a6c1e30 100644 --- a/modules/setting/queue.go +++ b/modules/setting/queue.go @@ -30,7 +30,7 @@ func GetQueueSettings(rootCfg ConfigProvider, name string) (QueueSettings, error queueSettingsDefault := QueueSettings{ Type: "level", // dummy, channel, level, redis Datadir: "queues/common", // relative to AppDataPath - Length: 100, // queue length before a channel queue will block + Length: 100000, // queue length before a channel queue will block QueueName: "_queue", SetName: "_unique", diff --git a/modules/setting/security.go b/modules/setting/security.go index 90f614d4cd..92caa05fad 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -19,7 +19,6 @@ var ( SecretKey string InternalToken string // internal access token LogInRememberDays int - CookieUserName string CookieRememberName string ReverseProxyAuthUser string ReverseProxyAuthEmail string @@ -104,7 +103,6 @@ func loadSecurityFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("security") InstallLock = HasInstallLock(rootCfg) LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) - CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome") SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") if SecretKey == "" { // FIXME: https://github.com/go-gitea/gitea/issues/16832 diff --git a/modules/setting/server.go b/modules/setting/server.go index 08eb82fb3d..d053fee5e7 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -81,7 +81,6 @@ var ( StaticCacheTime time.Duration EnableGzip bool LandingPageURL LandingPage - LandingPageCustom string UnixSocketPermission uint32 EnablePprof bool PprofDataPath string @@ -103,7 +102,6 @@ var ( StaticURLPrefix string AbsoluteAssetURL string - HasRobotsTxt bool ManifestData string ) diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 41438db40d..231698bf60 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -76,8 +76,8 @@ var UI = struct { CodeCommentLines: 4, ReactionMaxUserNum: 10, MaxDisplayFileSize: 8388608, - DefaultTheme: `auto`, - Themes: []string{`auto`, `gitea`, `arc-green`}, + DefaultTheme: `gitea-auto`, + Themes: []string{`gitea-auto`, `gitea-light`, `gitea-dark`}, Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 3246993bb1..b58ab67dc7 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -71,6 +71,11 @@ func convertMinioErr(err error) error { return err } +var getBucketVersioning = func(ctx context.Context, minioClient *minio.Client, bucket string) error { + _, err := minioClient.GetBucketVersioning(ctx, bucket) + return err +} + // NewMinioStorage returns a minio storage func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, error) { config := cfg.MinioConfig @@ -90,6 +95,23 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, return nil, convertMinioErr(err) } + // The GetBucketVersioning is only used for checking whether the Object Storage parameters are generally good. It doesn't need to succeed. + // The assumption is that if the API returns the HTTP code 400, then the parameters could be incorrect. + // Otherwise even if the request itself fails (403, 404, etc), the code should still continue because the parameters seem "good" enough. + // Keep in mind that GetBucketVersioning requires "owner" to really succeed, so it can't be used to check the existence. + // Not using "BucketExists (HeadBucket)" because it doesn't include detailed failure reasons. + err = getBucketVersioning(ctx, minioClient, config.Bucket) + if err != nil { + errResp, ok := err.(minio.ErrorResponse) + if !ok { + return nil, err + } + if errResp.StatusCode == http.StatusBadRequest { + log.Error("S3 storage connection failure at %s:%s with base path %s and region: %s", config.Endpoint, config.Bucket, config.Location, errResp.Message) + return nil, err + } + } + // Check to see if we already own this bucket exists, err := minioClient.BucketExists(ctx, config.Bucket) if err != nil { @@ -114,9 +136,18 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, } func (m *MinioStorage) buildMinioPath(p string) string { - p = util.PathJoinRelX(m.basePath, p) + p = strings.TrimPrefix(util.PathJoinRelX(m.basePath, p), "/") // object store doesn't use slash for root path if p == "." { - p = "" // minio doesn't use dot as relative path + p = "" // object store doesn't use dot as relative path + } + return p +} + +func (m *MinioStorage) buildMinioDirPrefix(p string) string { + // ending slash is required for avoiding matching like "foo/" and "foobar/" with prefix "foo" + p = m.buildMinioPath(p) + "/" + if p == "/" { + p = "" // object store doesn't use slash for root path } return p } @@ -215,20 +246,11 @@ func (m *MinioStorage) URL(path, name string) (*url.URL, error) { // IterateObjects iterates across the objects in the miniostorage func (m *MinioStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error { opts := minio.GetObjectOptions{} - lobjectCtx, cancel := context.WithCancel(m.ctx) - defer cancel() - - basePath := m.basePath - if dirName != "" { - // ending slash is required for avoiding matching like "foo/" and "foobar/" with prefix "foo" - basePath = m.buildMinioPath(dirName) + "/" - } - - for mObjInfo := range m.client.ListObjects(lobjectCtx, m.bucket, minio.ListObjectsOptions{ - Prefix: basePath, + for mObjInfo := range m.client.ListObjects(m.ctx, m.bucket, minio.ListObjectsOptions{ + Prefix: m.buildMinioDirPrefix(dirName), Recursive: true, }) { - object, err := m.client.GetObject(lobjectCtx, m.bucket, mObjInfo.Key, opts) + object, err := m.client.GetObject(m.ctx, m.bucket, mObjInfo.Key, opts) if err != nil { return convertMinioErr(err) } diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go index af392b7e22..c6fbb91ab4 100644 --- a/modules/storage/minio_test.go +++ b/modules/storage/minio_test.go @@ -4,10 +4,15 @@ package storage import ( + "context" + "net/http" "os" "testing" "code.gitea.io/gitea/modules/setting" + + "github.com/minio/minio-go/v7" + "github.com/stretchr/testify/assert" ) func TestMinioStorageIterator(t *testing.T) { @@ -25,3 +30,65 @@ func TestMinioStorageIterator(t *testing.T) { }, }) } + +func TestMinioStoragePath(t *testing.T) { + m := &MinioStorage{basePath: ""} + assert.Equal(t, "", m.buildMinioPath("/")) + assert.Equal(t, "", m.buildMinioPath(".")) + assert.Equal(t, "a", m.buildMinioPath("/a")) + assert.Equal(t, "a/b", m.buildMinioPath("/a/b/")) + assert.Equal(t, "", m.buildMinioDirPrefix("")) + assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/")) + + m = &MinioStorage{basePath: "/"} + assert.Equal(t, "", m.buildMinioPath("/")) + assert.Equal(t, "", m.buildMinioPath(".")) + assert.Equal(t, "a", m.buildMinioPath("/a")) + assert.Equal(t, "a/b", m.buildMinioPath("/a/b/")) + assert.Equal(t, "", m.buildMinioDirPrefix("")) + assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/")) + + m = &MinioStorage{basePath: "/base"} + assert.Equal(t, "base", m.buildMinioPath("/")) + assert.Equal(t, "base", m.buildMinioPath(".")) + assert.Equal(t, "base/a", m.buildMinioPath("/a")) + assert.Equal(t, "base/a/b", m.buildMinioPath("/a/b/")) + assert.Equal(t, "base/", m.buildMinioDirPrefix("")) + assert.Equal(t, "base/a/", m.buildMinioDirPrefix("/a/")) + + m = &MinioStorage{basePath: "/base/"} + assert.Equal(t, "base", m.buildMinioPath("/")) + assert.Equal(t, "base", m.buildMinioPath(".")) + assert.Equal(t, "base/a", m.buildMinioPath("/a")) + assert.Equal(t, "base/a/b", m.buildMinioPath("/a/b/")) + assert.Equal(t, "base/", m.buildMinioDirPrefix("")) + assert.Equal(t, "base/a/", m.buildMinioDirPrefix("/a/")) +} + +func TestS3StorageBadRequest(t *testing.T) { + if os.Getenv("CI") == "" { + t.Skip("S3Storage not present outside of CI") + return + } + cfg := &setting.Storage{ + MinioConfig: setting.MinioStorageConfig{ + Endpoint: "minio:9000", + AccessKeyID: "123456", + SecretAccessKey: "12345678", + Bucket: "bucket", + Location: "us-east-1", + }, + } + message := "ERROR" + old := getBucketVersioning + defer func() { getBucketVersioning = old }() + getBucketVersioning = func(ctx context.Context, minioClient *minio.Client, bucket string) error { + return minio.ErrorResponse{ + StatusCode: http.StatusBadRequest, + Code: "FixtureError", + Message: message, + } + } + _, err := NewStorage(setting.MinioStorageType, cfg) + assert.ErrorContains(t, err, message) +} diff --git a/modules/structs/commit_status.go b/modules/structs/commit_status.go index fda795dca6..dc880ef5eb 100644 --- a/modules/structs/commit_status.go +++ b/modules/structs/commit_status.go @@ -16,13 +16,16 @@ const ( CommitStatusError CommitStatusState = "error" // CommitStatusFailure is for when the CommitStatus is Failure CommitStatusFailure CommitStatusState = "failure" + // CommitStatusWarning is for when the CommitStatus is Warning + CommitStatusWarning CommitStatusState = "warning" ) var commitStatusPriorities = map[CommitStatusState]int{ CommitStatusError: 0, CommitStatusFailure: 1, - CommitStatusPending: 2, - CommitStatusSuccess: 3, + CommitStatusWarning: 2, + CommitStatusPending: 3, + CommitStatusSuccess: 4, } func (css CommitStatusState) String() string { @@ -32,7 +35,7 @@ func (css CommitStatusState) String() string { // NoBetterThan returns true if this State is no better than the given State // This function only handles the states defined in CommitStatusPriorities func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool { - // NoBetterThan only handles the 4 states above + // NoBetterThan only handles the 5 states above if _, exist := commitStatusPriorities[css]; !exist { return false } @@ -63,3 +66,8 @@ func (css CommitStatusState) IsError() bool { func (css CommitStatusState) IsFailure() bool { return css == CommitStatusFailure } + +// IsWarning represents if commit status state is warning +func (css CommitStatusState) IsWarning() bool { + return css == CommitStatusWarning +} diff --git a/modules/structs/mirror.go b/modules/structs/mirror.go index 55cd133a4f..8259583cde 100644 --- a/modules/structs/mirror.go +++ b/modules/structs/mirror.go @@ -3,6 +3,8 @@ package structs +import "time" + // CreatePushMirrorOption represents need information to create a push mirror of a repository. type CreatePushMirrorOption struct { RemoteAddress string `json:"remote_address"` @@ -15,12 +17,14 @@ type CreatePushMirrorOption struct { // PushMirror represents information of a push mirror // swagger:model type PushMirror struct { - RepoName string `json:"repo_name"` - RemoteName string `json:"remote_name"` - RemoteAddress string `json:"remote_address"` - CreatedUnix string `json:"created"` - LastUpdateUnix string `json:"last_update"` - LastError string `json:"last_error"` - Interval string `json:"interval"` - SyncOnCommit bool `json:"sync_on_commit"` + RepoName string `json:"repo_name"` + RemoteName string `json:"remote_name"` + RemoteAddress string `json:"remote_address"` + // swagger:strfmt date-time + CreatedUnix time.Time `json:"created"` + // swagger:strfmt date-time + LastUpdateUnix *time.Time `json:"last_update"` + LastError string `json:"last_error"` + Interval string `json:"interval"` + SyncOnCommit bool `json:"sync_on_commit"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 6a2ba4836b..3974c4db3a 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -63,6 +63,7 @@ type Repository struct { Language string `json:"language"` LanguagesURL string `json:"languages_url"` HTMLURL string `json:"html_url"` + URL string `json:"url"` Link string `json:"link"` SSHURL string `json:"ssh_url"` CloneURL string `json:"clone_url"` diff --git a/modules/system/appstate.go b/modules/system/appstate.go index 765501d432..e065688a4f 100644 --- a/modules/system/appstate.go +++ b/modules/system/appstate.go @@ -3,10 +3,12 @@ package system +import "context" + // StateStore is the interface to get/set app state items type StateStore interface { - Get(item StateItem) error - Set(item StateItem) error + Get(ctx context.Context, item StateItem) error + Set(ctx context.Context, item StateItem) error } // StateItem provides the name for a state item. the name will be used to generate filenames, etc diff --git a/modules/system/appstate_test.go b/modules/system/appstate_test.go index 65a6e50003..d4b9e167c2 100644 --- a/modules/system/appstate_test.go +++ b/modules/system/appstate_test.go @@ -4,9 +4,9 @@ package system import ( - "path/filepath" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -14,8 +14,7 @@ import ( func TestMain(m *testing.M) { unittest.MainTest(m, &unittest.TestOptions{ - GiteaRootPath: filepath.Join("..", ".."), - FixtureFiles: []string{""}, // load nothing + FixtureFiles: []string{""}, // load nothing }) } @@ -42,25 +41,25 @@ func TestAppStateDB(t *testing.T) { as := &DBStore{} item1 := new(testItem1) - assert.NoError(t, as.Get(item1)) + assert.NoError(t, as.Get(db.DefaultContext, item1)) assert.Equal(t, "", item1.Val1) assert.EqualValues(t, 0, item1.Val2) item1 = new(testItem1) item1.Val1 = "a" item1.Val2 = 2 - assert.NoError(t, as.Set(item1)) + assert.NoError(t, as.Set(db.DefaultContext, item1)) item2 := new(testItem2) item2.K = "V" - assert.NoError(t, as.Set(item2)) + assert.NoError(t, as.Set(db.DefaultContext, item2)) item1 = new(testItem1) - assert.NoError(t, as.Get(item1)) + assert.NoError(t, as.Get(db.DefaultContext, item1)) assert.Equal(t, "a", item1.Val1) assert.EqualValues(t, 2, item1.Val2) item2 = new(testItem2) - assert.NoError(t, as.Get(item2)) + assert.NoError(t, as.Get(db.DefaultContext, item2)) assert.Equal(t, "V", item2.K) } diff --git a/modules/system/db.go b/modules/system/db.go index 3f2e4c59bb..05e9de0ae8 100644 --- a/modules/system/db.go +++ b/modules/system/db.go @@ -4,6 +4,8 @@ package system import ( + "context" + "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/json" @@ -14,8 +16,8 @@ import ( type DBStore struct{} // Get reads the state item -func (f *DBStore) Get(item StateItem) error { - content, err := system.GetAppStateContent(item.Name()) +func (f *DBStore) Get(ctx context.Context, item StateItem) error { + content, err := system.GetAppStateContent(ctx, item.Name()) if err != nil { return err } @@ -26,10 +28,10 @@ func (f *DBStore) Get(item StateItem) error { } // Set saves the state item -func (f *DBStore) Set(item StateItem) error { +func (f *DBStore) Set(ctx context.Context, item StateItem) error { b, err := json.Marshal(item) if err != nil { return err } - return system.SaveAppStateContent(item.Name(), util.BytesToReadOnlyString(b)) + return system.SaveAppStateContent(ctx, item.Name(), util.BytesToReadOnlyString(b)) } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index bb1411cbfd..235fd96b73 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -12,6 +12,7 @@ import ( "strings" "time" + 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/markup" @@ -131,8 +132,11 @@ func NewFuncMap() template.FuncMap { "DisableImportLocal": func() bool { return !setting.ImportLocalPaths }, - "DefaultTheme": func() string { - return setting.UI.DefaultTheme + "ThemeName": func(user *user_model.User) string { + if user == nil || user.Theme == "" { + return setting.UI.DefaultTheme + } + return user.Theme }, "NotificationSettings": func() map[string]any { return map[string]any{ diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go index 7700a13932..6c1b4ab240 100644 --- a/modules/templates/util_misc.go +++ b/modules/templates/util_misc.go @@ -58,11 +58,11 @@ func IsMultilineCommitMessage(msg string) bool { // Actioner describes an action type Actioner interface { GetOpType() activities_model.ActionType - GetActUserName() string - GetRepoUserName() string - GetRepoName() string - GetRepoPath() string - GetRepoLink() string + GetActUserName(ctx context.Context) string + GetRepoUserName(ctx context.Context) string + GetRepoName(ctx context.Context) string + GetRepoPath(ctx context.Context) string + GetRepoLink(ctx context.Context) string GetBranch() string GetContent() string GetCreate() time.Time @@ -74,27 +74,31 @@ func ActionIcon(opType activities_model.ActionType) string { switch opType { case activities_model.ActionCreateRepo, activities_model.ActionTransferRepo, activities_model.ActionRenameRepo: return "repo" - case activities_model.ActionCommitRepo, activities_model.ActionPushTag, activities_model.ActionDeleteTag, activities_model.ActionDeleteBranch: + case activities_model.ActionCommitRepo: return "git-commit" - case activities_model.ActionCreateIssue: - return "issue-opened" - case activities_model.ActionCreatePullRequest: - return "git-pull-request" - case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: - return "comment-discussion" + case activities_model.ActionDeleteBranch: + return "git-branch" case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: return "git-merge" - case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: + case activities_model.ActionCreatePullRequest: + return "git-pull-request" + case activities_model.ActionClosePullRequest: + return "git-pull-request-closed" + case activities_model.ActionCreateIssue: + return "issue-opened" + case activities_model.ActionCloseIssue: return "issue-closed" case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: return "issue-reopened" + case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: + return "comment-discussion" case activities_model.ActionMirrorSyncPush, activities_model.ActionMirrorSyncCreate, activities_model.ActionMirrorSyncDelete: return "mirror" case activities_model.ActionApprovePullRequest: return "check" case activities_model.ActionRejectPullRequest: - return "diff" - case activities_model.ActionPublishRelease: + return "file-diff" + case activities_model.ActionPublishRelease, activities_model.ActionPushTag, activities_model.ActionDeleteTag: return "tag" case activities_model.ActionPullReviewDismissed: return "x" diff --git a/modules/updatechecker/update_checker.go b/modules/updatechecker/update_checker.go index bc3f93aad7..3c1e05d060 100644 --- a/modules/updatechecker/update_checker.go +++ b/modules/updatechecker/update_checker.go @@ -4,6 +4,7 @@ package updatechecker import ( + "context" "io" "net/http" @@ -58,31 +59,31 @@ func GiteaUpdateChecker(httpEndpoint string) error { return err } - return UpdateRemoteVersion(respData.Latest.Version) + return UpdateRemoteVersion(req.Context(), respData.Latest.Version) } // UpdateRemoteVersion updates the latest available version of Gitea -func UpdateRemoteVersion(version string) (err error) { - return system.AppState.Set(&CheckerState{LatestVersion: version}) +func UpdateRemoteVersion(ctx context.Context, version string) (err error) { + return system.AppState.Set(ctx, &CheckerState{LatestVersion: version}) } // GetRemoteVersion returns the current remote version (or currently installed version if fail to fetch from DB) -func GetRemoteVersion() string { +func GetRemoteVersion(ctx context.Context) string { item := new(CheckerState) - if err := system.AppState.Get(item); err != nil { + if err := system.AppState.Get(ctx, item); err != nil { return "" } return item.LatestVersion } // GetNeedUpdate returns true whether a newer version of Gitea is available -func GetNeedUpdate() bool { +func GetNeedUpdate(ctx context.Context) bool { curVer, err := version.NewVersion(setting.AppVer) if err != nil { // return false to fail silently return false } - remoteVerStr := GetRemoteVersion() + remoteVerStr := GetRemoteVersion(ctx) if remoteVerStr == "" { // no remote version is known return false diff --git a/modules/util/path.go b/modules/util/path.go index 58258560dd..e8537fb6b9 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -225,6 +225,7 @@ func isOSWindows() bool { var driveLetterRegexp = regexp.MustCompile("/[A-Za-z]:/") // FileURLToPath extracts the path information from a file://... url. +// It returns an error only if the URL is not a file URL. func FileURLToPath(u *url.URL) (string, error) { if u.Scheme != "file" { return "", errors.New("URL scheme is not 'file': " + u.String()) diff --git a/modules/util/url.go b/modules/util/url.go index 75fcf634a9..62370339c8 100644 --- a/modules/util/url.go +++ b/modules/util/url.go @@ -39,3 +39,12 @@ func URLJoin(base string, elems ...string) string { } return joinedURL } + +func SanitizeURL(s string) (string, error) { + u, err := url.Parse(s) + if err != nil { + return "", err + } + u.User = nil + return u.String(), nil +} diff --git a/modules/web/middleware/data.go b/modules/web/middleware/data.go index 9497dc02e2..08d83f94be 100644 --- a/modules/web/middleware/data.go +++ b/modules/web/middleware/data.go @@ -47,8 +47,6 @@ func GetContextData(c context.Context) ContextData { func CommonTemplateContextData() ContextData { return ContextData{ - "IsLandingPageHome": setting.LandingPageURL == setting.LandingPageHome, - "IsLandingPageExplore": setting.LandingPageURL == setting.LandingPageExplore, "IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations, "ShowRegistrationButton": setting.Service.ShowRegistrationButton, diff --git a/options/license/Adobe-Utopia b/options/license/Adobe-Utopia new file mode 100644 index 0000000000..4aa04503b5 --- /dev/null +++ b/options/license/Adobe-Utopia @@ -0,0 +1,12 @@ +Permission to use, reproduce, display and distribute the listed typefaces +is hereby granted, provided that the Adobe Copyright notice appears in all +whole and partial copies of the software and that the following trademark +symbol and attribution appear in all unmodified copies of the software: + +The Adobe typefaces (Type 1 font program, bitmaps and Adobe Font Metric +files) donated are: + + Utopia Regular + Utopia Italic + Utopia Bold + Utopia Bold Italic diff --git a/options/license/BSD-3-Clause-HP b/options/license/BSD-3-Clause-HP new file mode 100644 index 0000000000..e16195729a --- /dev/null +++ b/options/license/BSD-3-Clause-HP @@ -0,0 +1,23 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the HP nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PATENT INFRINGEMENT; PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/options/license/BSD-3-Clause-Sun b/options/license/BSD-3-Clause-Sun new file mode 100644 index 0000000000..1d86449d90 --- /dev/null +++ b/options/license/BSD-3-Clause-Sun @@ -0,0 +1,29 @@ +Copyright (c) 2001-2013 Oracle and/or its affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistribution in binary form must reproduct the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +Neither the name of Sun Microsystems, Inc. or the names of +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +This software is provided "AS IS," without a warranty of any kind. ALL +EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, +INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND +ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES OR LIABILITIES +SUFFERED BY LICENSEE AS A RESULT OF OR RELATING TO USE, MODIFICATION +OR DISTRIBUTION OF THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL +SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, +OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR +PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF +LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, +EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/options/license/BSD-3-Clause-flex b/options/license/BSD-3-Clause-flex new file mode 100644 index 0000000000..684b011026 --- /dev/null +++ b/options/license/BSD-3-Clause-flex @@ -0,0 +1,42 @@ +Flex carries the copyright used for BSD software, slightly modified +because it originated at the Lawrence Berkeley (not Livermore!) Laboratory, +which operates under a contract with the Department of Energy: + +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 The Flex Project. + +Copyright (c) 1990, 1997 The Regents of the University of California. +All rights reserved. + +This code is derived from software contributed to Berkeley by +Vern Paxson. + +The United States Government has rights in this work pursuant +to contract no. DE-AC03-76SF00098 between the United States +Department of Energy and the University of California. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +Neither the name of the University nor the names of its contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. + +This basically says "do whatever you please with this software except +remove this notice or take advantage of the University's (or the flex +authors') name". + +Note that the "flex.skl" scanner skeleton carries no copyright notice. +You are free to do whatever you please with scanners generated using flex; +for them, you are not even bound by the above copyright. diff --git a/options/license/BSD-Inferno-Nettverk b/options/license/BSD-Inferno-Nettverk new file mode 100644 index 0000000000..d10fe158a1 --- /dev/null +++ b/options/license/BSD-Inferno-Nettverk @@ -0,0 +1,41 @@ + Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + 2017, 2018, 2019, 2020 + Inferno Nettverk A/S, Norway. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. The above copyright notice, this list of conditions and the following + disclaimer must appear in all copies of the software, derivative works + or modified versions, and any portions thereof, aswell as in all + supporting documentation. + 2. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by + Inferno Nettverk A/S, Norway. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Inferno Nettverk A/S requests users of this software to return to + + Software Distribution Coordinator or sdc@inet.no + Inferno Nettverk A/S + Oslo Research Park + Gaustadalléen 21 + NO-0349 Oslo + Norway + + any improvements or extensions that they make and grant Inferno Nettverk A/S + the rights to redistribute these changes. diff --git a/options/license/BSD-Systemics b/options/license/BSD-Systemics new file mode 100644 index 0000000000..6ca8a26c33 --- /dev/null +++ b/options/license/BSD-Systemics @@ -0,0 +1,39 @@ +Copyright (C) 1995, 1996 Systemics Ltd (http://www.systemics.com/) +All rights reserved. + +This library and applications are FREE FOR COMMERCIAL AND NON-COMMERCIAL USE +as long as the following conditions are adhered to. + +Copyright remains with Systemics Ltd, and as such any Copyright notices +in the code are not to be removed. If this code is used in a product, +Systemics should be given attribution as the author of the parts used. +This can be in the form of a textual message at program startup or +in documentation (online or textual) provided with the package. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by Systemics Ltd (http://www.systemics.com/) + +THIS SOFTWARE IS PROVIDED BY SYSTEMICS LTD ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +The licence and distribution terms for any publically available version or +derivative of this code cannot be changed. i.e. this code cannot simply be +copied and put under another distribution licence [including the GNU Public Licence.] diff --git a/options/license/Cronyx b/options/license/Cronyx new file mode 100644 index 0000000000..10fa8e7067 --- /dev/null +++ b/options/license/Cronyx @@ -0,0 +1,11 @@ +This package contains a set of Russian fonts for X11 Release 6. +Copyright (C) 1994-1995 Cronyx Ltd. +Changes Copyright (C) 1996 by Sergey Vovk +Changes Copyright (C) 1999-2000 by Serge Winitzki +Changes Copyright (C) 1996-2000 by Andrey A. Chernov, Moscow, Russia. + +This software may be used, modified, copied, distributed, and sold, +in both source and binary form provided that the copyright +and these terms are retained. Under no circumstances is the author +responsible for the proper functioning of this software, nor does +the author assume any responsibility for damages incurred with its use. diff --git a/options/license/Crossword b/options/license/Crossword index 6be940c33b..35d95a79d7 100644 --- a/options/license/Crossword +++ b/options/license/Crossword @@ -1,5 +1,5 @@ Copyright (C) 1995-2009 Gerd Neugebauer cwpuzzle.dtx is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. No author or distributor accepts responsibility to anyone for the consequences of using it or for whether it serves any particular purpose or works at all, unless he says so in writing. -. + Everyone is granted permission to copy, modify and redistribute cwpuzzle.dtx, provided this copyright notice is preserved and any modifications are indicated. diff --git a/options/license/DL-DE-ZERO-2.0 b/options/license/DL-DE-ZERO-2.0 new file mode 100644 index 0000000000..7daacde13d --- /dev/null +++ b/options/license/DL-DE-ZERO-2.0 @@ -0,0 +1,25 @@ +DL-DE->Zero-2.0 +Datenlizenz Deutschland – Zero – Version 2.0 + +Jede Nutzung ist ohne Einschränkungen oder Bedingungen zulässig. + +Die bereitgestellten Daten und Metadaten dürfen für die kommerzielle und nicht kommerzielle Nutzung insbesondere + + vervielfältigt, ausgedruckt, präsentiert, verändert, bearbeitet sowie an Dritte übermittelt werden; + mit eigenen Daten und Daten Anderer zusammengeführt und zu selbständigen neuen Datensätzen verbunden werden; + in interne und externe Geschäftsprozesse, Produkte und Anwendungen in öffentlichen und nicht öffentlichen elektronischen Netzwerken eingebunden werden. + + +Data licence Germany – Zero – version 2.0 + +Any use is permitted without restrictions or conditions. + +The data and meta-data provided may, for commercial and non-commercial use, in particular + + be copied, printed, presented, altered, processed and transmitted to third parties; + be merged with own data and with the data of others and be combined to form new and independent datasets; + be integrated in internal and external business processes, products and applications in public and non-public electronic networks. + + + +URL: https://www.govdata.de/dl-de/zero-2-0 diff --git a/options/license/FBM b/options/license/FBM new file mode 100644 index 0000000000..68d9149b90 --- /dev/null +++ b/options/license/FBM @@ -0,0 +1,6 @@ +Portions of this code Copyright (C) 1989 by Michael Mauldin. +Permission is granted to use this file in whole or in +part for any purpose, educational, recreational or commercial, +provided that this copyright notice is retained unchanged. +This software is available to all free of charge by anonymous +FTP and in the UUNET archives. diff --git a/options/license/Ferguson-Twofish b/options/license/Ferguson-Twofish new file mode 100644 index 0000000000..43bb00c3ee --- /dev/null +++ b/options/license/Ferguson-Twofish @@ -0,0 +1,15 @@ + The author hereby grants a perpetual license to everybody to + use this code for any purpose as long as the copyright message is included + in the source code of this or any derived work. + + Yes, this means that you, your company, your club, and anyone else + can use this code anywhere you want. You can change it and distribute it + under the GPL, include it in your commercial product without releasing + the source code, put it on the web, etc. + The only thing you cannot do is remove my copyright message, + or distribute any source code based on this implementation that does not + include my copyright message. + + I appreciate a mention in the documentation or credits, + but I understand if that is difficult to do. + I also appreciate it if you tell me where and why you used my code. diff --git a/options/license/Furuseth b/options/license/Furuseth new file mode 100644 index 0000000000..55feeef90b --- /dev/null +++ b/options/license/Furuseth @@ -0,0 +1,13 @@ +Portions Copyright 1999-2008 Howard Y.H. Chu. +Portions Copyright 1999-2008 Symas Corporation. +Portions Copyright 1998-2003 Hallvard B. Furuseth. +Portions Copyright 2007-2011 Gavin Henry. +Portions Copyright 2007-2011 Suretec Systems Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that this notice is preserved. +The names of the copyright holders may not be used to endorse or +promote products derived from this software without their specific +prior written permission. This software is provided ``as is'' +without express or implied warranty. diff --git a/options/license/GCC-exception-2.0-note b/options/license/GCC-exception-2.0-note new file mode 100644 index 0000000000..654099aac0 --- /dev/null +++ b/options/license/GCC-exception-2.0-note @@ -0,0 +1,16 @@ + In addition to the permissions in the GNU Lesser General Public + License, the Free Software Foundation gives you unlimited + permission to link the compiled version of this file with other + programs, and to distribute those programs without any restriction + coming from the use of this file. (The GNU Lesser General Public + License restrictions do apply in other respects; for example, they + cover modification of the file, and distribution when not linked + into another program.) + + Note that people who make modified versions of this file are not + obligated to grant this special exception for their modified + versions; it is their choice whether to do so. The GNU Lesser + General Public License gives permission to release a modified + version without this exception; this exception also makes it + possible to release a modified version which carries forward this + exception. diff --git a/options/license/HPND b/options/license/HPND index 74abede5f6..ff9ae1b713 100644 --- a/options/license/HPND +++ b/options/license/HPND @@ -1,7 +1,7 @@ Historical Permission Notice and Disclaimer - +Copyright -Permission to use, copy, modify and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies, and that both that the copyright notice and this permission notice appear in supporting documentation, and that the name of or not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. +Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of or not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,. IN NO EVENT SHALL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/options/license/HPND-DEC b/options/license/HPND-DEC new file mode 100644 index 0000000000..d014f1fabc --- /dev/null +++ b/options/license/HPND-DEC @@ -0,0 +1,22 @@ +COPYRIGHT 1990 +DIGITAL EQUIPMENT CORPORATION +MAYNARD, MASSACHUSETTS +ALL RIGHTS RESERVED. + +THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION. +DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE +FOR ANY PURPOSE. IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED +WARRANTY. + +IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT +RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN +ADDITION TO THAT SET FORTH ABOVE. + +Permission to use, copy, modify, and distribute this software and +its documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies +and that both that copyright notice and this permission notice appear in supporting +documentation, +and that the name of Digital Equipment Corporation not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. diff --git a/options/license/HPND-Pbmplus b/options/license/HPND-Pbmplus new file mode 100644 index 0000000000..5627d2646f --- /dev/null +++ b/options/license/HPND-Pbmplus @@ -0,0 +1,8 @@ +Copyright (C) 1991 by Jef Poskanzer. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. This software is provided "as is" without express or +implied warranty. diff --git a/options/license/HPND-UC b/options/license/HPND-UC new file mode 100644 index 0000000000..adfbd23862 --- /dev/null +++ b/options/license/HPND-UC @@ -0,0 +1,8 @@ +Copyright 1989 Regents of the University of California + +Permission to use, +copy, modify, and distribute this software and its documentation for any +purpose and without fee is hereby granted, provided that the above +copyright notice appear in all copies. The University of California makes +no representations about the suitability of this software for any purpose. +It is provided "as is" without express or implied warranty. diff --git a/options/license/HPND-doc b/options/license/HPND-doc new file mode 100644 index 0000000000..bd85a2816e --- /dev/null +++ b/options/license/HPND-doc @@ -0,0 +1,8 @@ +Copyright + +Permission to use, copy, modify, and distribute this documentation for +any purpose and without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + makes no representations about the suitability for +any purpose of the information in this document. This documentation is +provided ``as is'' without express or implied warranty. diff --git a/options/license/HPND-doc-sell b/options/license/HPND-doc-sell new file mode 100644 index 0000000000..ad4aed3e60 --- /dev/null +++ b/options/license/HPND-doc-sell @@ -0,0 +1,9 @@ +Copyright + +Permission to use, copy, modify, distribute, and sell this +documentation for any purpose is hereby granted without fee, +provided that the above copyright notice and this permission +notice appear in all copies. +makes no representations about the suitability for any purpose +of the information in this document. This documentation +is provided "as is" without express or implied warranty. diff --git a/options/license/HPND-export-US-modify b/options/license/HPND-export-US-modify new file mode 100644 index 0000000000..3c62651c0f --- /dev/null +++ b/options/license/HPND-export-US-modify @@ -0,0 +1,24 @@ +Copyright (C) 1994 CyberSAFE Corporation. +Copyright 1990,1991,2007,2008 by the Massachusetts +Institute of Technology. +All Rights Reserved. + +Export of this software from the United States of America may +require a specific license from the United States Government. It +is the responsibility of any person or organization +contemplating export to obtain such a license before exporting. + +WITHIN THAT CONSTRAINT, permission to use, copy, modify, and +distribute this software and its documentation for any purpose and +without fee is hereby granted, provided that the above copyright +notice appear in all copies and that both that copyright notice and +this permission notice appear in supporting documentation, and that +the name of M.I.T. not be used in advertising or publicity +pertaining to distribution of the software without specific, +written prior permission. Furthermore if you modify this software +you must label your software as modified software and not +distribute it in such a fashion that it might be confused with the +original M.I.T. software. Neither M.I.T., the Open Computing +Security Group, nor CyberSAFE Corporation make any representations +about the suitability of this software for any purpose. It is +provided "as is" without express or implied warranty. diff --git a/options/license/HPND-sell-regexpr b/options/license/HPND-sell-regexpr new file mode 100644 index 0000000000..b0cd0fb112 --- /dev/null +++ b/options/license/HPND-sell-regexpr @@ -0,0 +1,9 @@ +Author: Tatu Ylonen + +Copyright (c) 1991 Tatu Ylonen, Espoo, Finland + +Permission to use, copy, modify, distribute, and sell this software +and its documentation for any purpose is hereby granted without +fee, provided that the above copyright notice appear in all copies. +This software is provided "as is" without express or implied +warranty. diff --git a/options/license/Kastrup b/options/license/Kastrup new file mode 100644 index 0000000000..46d1e9e0e4 --- /dev/null +++ b/options/license/Kastrup @@ -0,0 +1,3 @@ +Copyright(c) 2001 by David Kastrup +Any use of the code is permitted as long as this copyright +notice is preserved in the code. diff --git a/options/license/Lucida-Bitmap-Fonts b/options/license/Lucida-Bitmap-Fonts new file mode 100644 index 0000000000..35be63ed33 --- /dev/null +++ b/options/license/Lucida-Bitmap-Fonts @@ -0,0 +1,53 @@ +This is the LEGAL NOTICE pertaining to the Lucida fonts from Bigelow & Holmes: + + NOTICE TO USER: The source code, including the glyphs or icons + forming a par of the OPEN LOOK TM Graphic User Interface, on this + tape and in these files is copyrighted under U.S. and international + laws. Sun Microsystems, Inc. of Mountain View, California owns + the copyright and has design patents pending on many of the icons. + AT&T is the owner of the OPEN LOOK trademark associated with the + materials on this tape. Users and possessors of this source code + are hereby granted a nonexclusive, royalty-free copyright and + design patent license to use this code in individual and + commercial software. A royalty-free, nonexclusive trademark + license to refer to the code and output as "OPEN LOOK" compatible + is available from AT&T if, and only if, the appearance of the + icons or glyphs is not changed in any manner except as absolutely + necessary to accommodate the standard resolution of the screen or + other output device, the code and output is not changed except as + authorized herein, and the code and output is validated by AT&T. + Bigelow & Holmes is the owner of the Lucida (R) trademark for the + fonts and bit-mapped images associated with the materials on this + tape. Users are granted a royalty-free, nonexclusive license to use + the trademark only to identify the fonts and bit-mapped images if, + and only if, the fonts and bit-mapped images are not modified in any + way by the user. + + Any use of this source code must include, in the user documentation + and internal comments to the code, notices to the end user as + follows: + + (c) Copyright 1989 Sun Microsystems, Inc. Sun design patents + pending in the U.S. and foreign countries. OPEN LOOK is a + trademark of AT&T. Used by written permission of the owners. + + (c) Copyright Bigelow & Holmes 1986, 1985. Lucida is a registered + trademark of Bigelow & Holmes. Permission to use the Lucida + trademark is hereby granted only in association with the images + and fonts described in this file. + + SUN MICROSYSTEMS, INC., AT&T, AND BIGELOW & HOLMES + MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY OF + THIS SOURCE CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" + WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. + SUN MICROSYSTEMS, INC., AT&T AND BIGELOW & HOLMES, + SEVERALLY AND INDIVIDUALLY, DISCLAIM ALL WARRANTIES + WITH REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE. IN NO EVENT SHALL SUN MICROSYSTEMS, + INC., AT&T OR BIGELOW & HOLMES BE LIABLE FOR ANY + SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, + OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + WITH THE USE OR PERFORMANCE OF THIS SOURCE CODE. diff --git a/options/license/MPEG-SSG b/options/license/MPEG-SSG new file mode 100644 index 0000000000..a0b6f4ffff --- /dev/null +++ b/options/license/MPEG-SSG @@ -0,0 +1,23 @@ +Copyright (C) 1994, MPEG Software Simulation Group. All Rights Reserved. */ + +Disclaimer of Warranty + +These software programs are available to the user without any license fee or +royalty on an "as is" basis. The MPEG Software Simulation Group disclaims +any and all warranties, whether express, implied, or statuary, including any +implied warranties or merchantability or of fitness for a particular +purpose. In no event shall the copyright-holder be liable for any +incidental, punitive, or consequential damages of any kind whatsoever +arising from the use of these programs. + +This disclaimer of warranty extends to the user of these programs and user's +customers, employees, agents, transferees, successors, and assigns. + +The MPEG Software Simulation Group does not represent or warrant that the +programs furnished hereunder are free of infringement of any third-party +patents. + +Commercial implementations of MPEG-1 and MPEG-2 video, including shareware, +are subject to royalty fees to patent holders. Many of these patents are +general enough such that they are unavoidable regardless of implementation +design. diff --git a/options/license/McPhee-slideshow b/options/license/McPhee-slideshow new file mode 100644 index 0000000000..0ddf7ba350 --- /dev/null +++ b/options/license/McPhee-slideshow @@ -0,0 +1,6 @@ +Copyright 2001, Patrick TJ McPhee +everyone is welcome to use this code for any purpose, to modify it, and +to copy it in whole or in part for use in other macro sets, with the +conditions that this copyright notice be preserved with any significant +portion of the code, and that modifications to this file be clearly +marked. diff --git a/options/license/NPL-1.0 b/options/license/NPL-1.0 index 7a5030e9f7..65983791a2 100644 --- a/options/license/NPL-1.0 +++ b/options/license/NPL-1.0 @@ -8,20 +8,20 @@ NETSCAPE PUBLIC LICENSE Version 1.0 1.3. ``Covered Code'' means the Original Code or Modifications or the combination of the Original Code and Modifications, in each case including portions thereof. 1.4. ``Electronic Distribution Mechanism'' means a mechanism generally accepted in the software development community for the electronic transfer of data. 1.5. ``Executable'' means Covered Code in any form other than Source Code. - 1.6. ``Initial Developer'' means the individual or entity identified as the Initial Developer in the Source Code notice required byExhibit A. + 1.6. ``Initial Developer'' means the individual or entity identified as the Initial Developer in the Source Code notice required by Exhibit A. 1.7. ``Larger Work'' means a work which combines Covered Code or portions thereof with code not governed by the terms of this License. 1.8. ``License'' means this document. 1.9. ``Modifications'' means any addition to or deletion from the substance or structure of either the Original Code or any previous Modifications. When Covered Code is released as a series of files, a Modification is: A. Any addition to or deletion from the contents of a file containing Original Code or previous Modifications. B. Any new file that contains any part of the Original Code or previous Modifications. - 1.10. ``Original Code'' means Source Code of computer software code which is described in the Source Code notice required byExhibit A as Original Code, and which, at the time of its release under this License is not already Covered Code governed by this License. + 1.10. ``Original Code'' means Source Code of computer software code which is described in the Source Code notice required by Exhibit A as Original Code, and which, at the time of its release under this License is not already Covered Code governed by this License. 1.11. ``Source Code'' means the preferred form of the Covered Code for making modifications to it, including all modules it contains, plus any associated interface definition files, scripts used to control compilation and installation of an Executable, or a list of source code differential comparisons against either the Original Code or another well known, available Covered Code of the Contributor's choice. The Source Code can be in a compressed or archival form, provided the appropriate decompression or de-archiving software is widely available for no charge. 1.12. ``You'' means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License or a future version of this License issued under Section 6.1. For legal entities, ``You'' includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, ``control'' means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of fifty percent (50%) or more of the outstanding shares or beneficial ownership of such entity. 2. Source Code License. 2.1. The Initial Developer Grant. The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims: - a) to use, reproduce, modify, display, perform, sublicense and distribute the Original Code (or portions thereof) with or without Modifications, or as part of a Larger Work; and + (a) to use, reproduce, modify, display, perform, sublicense and distribute the Original Code (or portions thereof) with or without Modifications, or as part of a Larger Work; and (b) under patents now or hereafter owned or controlled by Initial Developer, to make, have made, use and sell (``Utilize'') the Original Code (or portions thereof), but solely to the extent that any such patent is reasonably necessary to enable You to Utilize the Original Code (or portions thereof) and not to any greater extent that may be necessary to Utilize further Modifications or combinations. 2.2. Contributor Grant. Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims: diff --git a/options/license/PADL b/options/license/PADL new file mode 100644 index 0000000000..84ba0b4db9 --- /dev/null +++ b/options/license/PADL @@ -0,0 +1,6 @@ +Portions (C) Copyright PADL Software Pty Ltd. 1999 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that this notice is preserved +and that due credit is given to PADL Software Pty Ltd. This software +is provided ``as is'' without express or implied warranty. diff --git a/options/license/SGI-OpenGL b/options/license/SGI-OpenGL new file mode 100644 index 0000000000..2b4c542aa0 --- /dev/null +++ b/options/license/SGI-OpenGL @@ -0,0 +1,34 @@ +(c) Copyright 1993, Silicon Graphics, Inc. +ALL RIGHTS RESERVED +Permission to use, copy, modify, and distribute this software for +any purpose and without fee is hereby granted, provided that the above +copyright notice appear in all copies and that both the copyright notice +and this permission notice appear in supporting documentation, and that +the name of Silicon Graphics, Inc. not be used in advertising +or publicity pertaining to distribution of the software without specific, +written prior permission. + +THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" +AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, +INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR +FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON +GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, +SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY +KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, +LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF +THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE +POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + +US Government Users Restricted Rights +Use, duplication, or disclosure by the Government is subject to +restrictions set forth in FAR 52.227.19(c)(2) or subparagraph +(c)(1)(ii) of the Rights in Technical Data and Computer Software +clause at DFARS 252.227-7013 and/or in similar or successor +clauses in the FAR or the DOD or NASA FAR Supplement. +Unpublished-- rights reserved under the copyright laws of the +United States. Contractor/manufacturer is Silicon Graphics, +Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + +OpenGL(TM) is a trademark of Silicon Graphics, Inc. diff --git a/options/license/UBDL-exception b/options/license/UBDL-exception new file mode 100644 index 0000000000..780ddcd775 --- /dev/null +++ b/options/license/UBDL-exception @@ -0,0 +1,59 @@ +UNMODIFIED BINARY DISTRIBUTION LICENCE + + +PREAMBLE + +The GNU General Public License provides a legal guarantee that +software covered by it remains free (in the sense of freedom, not +price). It achieves this guarantee by imposing obligations on anyone +who chooses to distribute the software. + +Some of these obligations may be seen as unnecessarily burdensome. In +particular, when the source code for the software is already publicly +and freely available, there is minimal value in imposing upon each +distributor the obligation to provide the complete source code (or an +equivalent written offer to provide the complete source code). + +This Licence allows for the distribution of unmodified binaries built +from publicly available source code, without imposing the obligations +of the GNU General Public License upon anyone who chooses to +distribute only the unmodified binaries built from that source code. + +The extra permissions granted by this Licence apply only to unmodified +binaries built from source code which has already been made available +to the public in accordance with the terms of the GNU General Public +Licence. Nothing in this Licence allows for the creation of +closed-source modified versions of the Program. Any modified versions +of the Program are subject to the usual terms and conditions of the +GNU General Public License. + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +This Licence applies to any Program or other work which contains a +notice placed by the copyright holder saying it may be distributed +under the terms of this Unmodified Binary Distribution Licence. All +terms used in the text of this Licence are to be interpreted as they +are used in version 2 of the GNU General Public License as published +by the Free Software Foundation. + +If you have made this Program available to the public in both source +code and executable form in accordance with the terms of the GNU +General Public License as published by the Free Software Foundation; +either version 2 of the License, or (at your option) any later +version, then you are hereby granted an additional permission to use, +copy, and distribute the unmodified executable form of this Program +(the "Unmodified Binary") without restriction, including the right to +permit persons to whom the Unmodified Binary is furnished to do +likewise, subject to the following conditions: + +- when started running, the Program must display an announcement which + includes the details of your existing publication of the Program + made in accordance with the terms of the GNU General Public License. + For example, the Program could display the URL of the publicly + available source code from which the Unmodified Binary was built. + +- when exercising your right to grant permissions under this Licence, + you do not need to refer directly to the text of this Licence, but + you may not grant permissions beyond those granted to you by this + Licence. diff --git a/options/license/fwlw b/options/license/fwlw new file mode 100644 index 0000000000..472a85a564 --- /dev/null +++ b/options/license/fwlw @@ -0,0 +1,5 @@ +Copyright (C) 1993,1995 by Donald Arseneau +Vancouver, Canada, email asnd@triumf.ca + +This software package may be freely used, transmitted, reproduced, or modified provided that +this notice is left intact. diff --git a/options/license/lsof b/options/license/lsof new file mode 100644 index 0000000000..279721a90a --- /dev/null +++ b/options/license/lsof @@ -0,0 +1,26 @@ +Copyright 2002 Purdue Research Foundation, West Lafayette, +Indiana 47907. All rights reserved. + +Written by Victor A. Abell + +This software is not subject to any license of the American +Telephone and Telegraph Company or the Regents of the +University of California. + +Permission is granted to anyone to use this software for +any purpose on any computer system, and to alter it and +redistribute it freely, subject to the following +restrictions: + +1. Neither the authors nor Purdue University are responsible + for any consequences of the use of this software. + +2. The origin of this software must not be misrepresented, + either by explicit claim or by omission. Credit to the + authors and Purdue University must appear in documentation + and sources. + +3. Altered versions must be plainly marked as such, and must + not be misrepresented as being the original software. + +4. This notice may not be removed or altered. diff --git a/options/license/magaz b/options/license/magaz new file mode 100644 index 0000000000..34d033c03e --- /dev/null +++ b/options/license/magaz @@ -0,0 +1,4 @@ +Copyright 1999-2011, Donald Arseneau, asnd@triumf.ca, Vancouver, Canada + +This software may be freely used, transmitted, reproduced, or modified provided that +the copyright notice and this permission is retained. diff --git a/options/license/pnmstitch b/options/license/pnmstitch new file mode 100644 index 0000000000..cb9dc762d9 --- /dev/null +++ b/options/license/pnmstitch @@ -0,0 +1,23 @@ +Copyright (c) 2002 Mark Salyzyn +All rights reserved. + +TERMS AND CONDITIONS OF USE + +Redistribution and use in source form, with or without modification, are +permitted provided that redistributions of source code must retain the +above copyright notice, this list of conditions and the following +disclaimer. + +This software is provided `as is' by Mark Salyzyn and any express or implied +warranties, including, but not limited to, the implied warranties of +merchantability and fitness for a particular purpose, are disclaimed. In no +event shall Mark Salyzyn be liable for any direct, indirect, incidental, +special, exemplary or consequential damages (including, but not limited to, +procurement of substitute goods or services; loss of use, data, or profits; +or business interruptions) however caused and on any theory of liability, +whether in contract, strict liability, or tort (including negligence or +otherwise) arising in any way out of the use of this software, even if +advised of the possibility of such damage. + +Any restrictions or encumberances added to this source code or derivitives, +is prohibited. diff --git a/options/license/python-ldap b/options/license/python-ldap new file mode 100644 index 0000000000..733e8cfc0a --- /dev/null +++ b/options/license/python-ldap @@ -0,0 +1,10 @@ +The python-ldap package is distributed under Python-style license. + +Standard disclaimer: + This software is made available by the author(s) to the public for free + and "as is". All users of this free software are solely and entirely + responsible for their own choice and use of this software for their + own purposes. By using this software, each user agrees that the + author(s) shall not be liable for damages of any kind in relation to + its use or performance. The author(s) do not warrant that this software + is fit for any purpose. diff --git a/options/license/ssh-keyscan b/options/license/ssh-keyscan new file mode 100644 index 0000000000..6c97472c1e --- /dev/null +++ b/options/license/ssh-keyscan @@ -0,0 +1,5 @@ +* Copyright 1995, 1996 by David Mazieres . +* +* Modification and redistribution in source and binary forms is +* permitted provided that due credit is given to the author and the +* OpenBSD project by leaving this copyright notice intact. diff --git a/options/license/swrule b/options/license/swrule new file mode 100644 index 0000000000..aebc5fd6d3 --- /dev/null +++ b/options/license/swrule @@ -0,0 +1 @@ +The style package is copyrighted but may be used and extended in any way, as long as a pointer to the original author is maintained. The author is not liable for any problem that may or may not result from using this package. Use at your own risk. diff --git a/options/license/ulem b/options/license/ulem new file mode 100644 index 0000000000..ee49efe8dd --- /dev/null +++ b/options/license/ulem @@ -0,0 +1,4 @@ +Copyright 1989-2019 by Donald Arseneau (Vancouver, Canada, asnd@triumf.ca) + +This software may be freely transmitted, reproduced, or modified +for any purpose provided that this copyright notice is left intact. diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 819e3e85e8..602fe0bce8 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -120,6 +120,8 @@ unpin=Odepnout artifacts=Artefakty +archived=Archivováno + concept_system_global=Globální concept_user_individual=Individuální concept_code_repository=Repozitář @@ -175,7 +177,6 @@ network_error=Chyba sítě [startpage] app_desc=Snadno přístupný vlastní Git install=Jednoduchá na instalaci -install_desc=Jednoduše spusťte binárku pro vaši platformu, nasaďte ji pomocí Docker, nebo ji získejte zbalíčku. platform=Multiplatformní platform_desc=Gitea běží všude, kde Go může kompilovat: Windows, macOS, Linux, ARM, atd. Vyberte si ten, který milujete! lightweight=Lehká @@ -574,7 +575,6 @@ overview=Přehled following=Sledovaní follow=Sledovat unfollow=Přestat sledovat -heatmap.loading=Načítání teplotní mapy… user_bio=Životopis disabled_public_activity=Tento uživatel zakázal veřejnou viditelnost aktivity. email_visibility.limited=Vaše e-mailová adresa je viditelná pro všechny ověřené uživatele @@ -644,7 +644,6 @@ choose_new_avatar=Vybrat nový avatar update_avatar=Aktualizovat avatar delete_current_avatar=Smazat aktuální avatar uploaded_avatar_not_a_image=Nahraný soubor není obrázek. -uploaded_avatar_is_too_big=Nahraný soubor překročil maximální velikost. update_avatar_success=Vaše avatar byl aktualizován. update_user_avatar_success=Uživatelův avatar byl aktualizován. @@ -944,6 +943,7 @@ delete_preexisting_success=Smazány nepřijaté soubory v %s blame_prior=Zobrazit blame před touto změnou author_search_tooltip=Zobrazí maximálně 30 uživatelů + transfer.accept=Přijmout převod transfer.accept_desc=Převést do „%s“ transfer.reject=Odmítnout převod @@ -1192,6 +1192,11 @@ commit.cherry-pick=Cherry-pick commit.cherry-pick-header=Cherry-pick: %s commit.cherry-pick-content=Vyberte větev pro Cherry-pick na: +commitstatus.error=Chyba +commitstatus.failure=Chyba +commitstatus.pending=Čekající +commitstatus.success=Úspěch + ext_issues=Přístup k externím úkolům ext_issues.desc=Odkaz na externí systém úkolů. @@ -1379,9 +1384,9 @@ issues.ref_reopening_from=`odkazoval/a na požadavek na natažen issues.ref_closed_from=`uzavřel/a tento úkol %[4]s %[2]s` issues.ref_reopened_from=`znovu otevřel/a tento úkol %[4]s %[2]s` issues.ref_from=`z %[1]s` -issues.poster=Autor -issues.collaborator=Spolupracovník -issues.owner=Vlastník +issues.author=Autor +issues.role.owner=Vlastník +issues.role.member=Člen issues.re_request_review=Znovu požádat o posouzení issues.is_stale=Od tohoto posouzení došlo ke změnám v tomto požadavku na natažení issues.remove_request_review=Odstranit žádost o posouzení @@ -1685,8 +1690,6 @@ milestones.modify=Aktualizovat milník milestones.deletion=Smazat milník milestones.deletion_desc=Odstranění milníku jej smaže ze všech souvisejících úkolů. Pokračovat? milestones.deletion_success=Milník byl odstraněn. -milestones.filter_sort.closest_due_date=Nejbližší datum dokončení -milestones.filter_sort.furthest_due_date=Nejvzdálenější datum dokončení milestones.filter_sort.least_complete=Nejméně dokončené milestones.filter_sort.most_complete=Nejvíce dokončené milestones.filter_sort.most_issues=Nejvíce úkolů @@ -1822,7 +1825,6 @@ settings.mirror_settings.push_mirror.remote_url=URL vzdáleného Git repozitář settings.mirror_settings.push_mirror.add=Přidat zrcadlo pro nahrání settings.sync_mirror=Synchronizovat nyní -settings.mirror_sync_in_progress=Právě probíhá synchronizace zrcadla. Zkuste to za chvíli. settings.site=Webová stránka settings.update_settings=Aktualizovat nastavení settings.update_mirror_settings=Aktualizovat nastavení zrcadla @@ -2127,7 +2129,6 @@ settings.tags.protection.allowed.teams=Povolené týmy settings.tags.protection.allowed.noone=Nikdo settings.tags.protection.create=Chránit značku settings.tags.protection.none=Neexistují žádné chráněné značky. -settings.tags.protection.pattern.description=Můžete použít jediné jméno nebo vzor glob nebo regulární výraz, který bude odpovídat více značek. Přečtěte si více v průvodci chráněnými značkami. settings.bot_token=Token pro robota settings.chat_id=ID chatu settings.matrix.homeserver_url=URL adresa Homeserveru @@ -2269,7 +2270,6 @@ release.add_tag=Vytvořit pouze značku release.tags_for=Značky pro %s branch.name=Jméno větve -branch.search=Hledat větve branch.delete_head=Smazat branch.delete_html=Smazat větev branch.create_branch=Vytvořit větev %s @@ -2465,9 +2465,7 @@ dashboard.deleted_branches_cleanup=Vyčistit odstraněné větve dashboard.update_migration_poster_id=Aktualizovat ID autora migrace dashboard.git_gc_repos=Provést úklid všech repozitářů dashboard.resync_all_sshkeys=Aktualizovat soubor „.ssh/authorized_keys“ pomocí SSH klíčů Gitea. -dashboard.resync_all_sshkeys.desc=(Není potřeba pro vestavěný SSH server.) dashboard.resync_all_sshprincipals=Aktualizovat soubor '.ssh/authorized_principals' pomocí Gitea SSH Principal certifikátů. -dashboard.resync_all_sshprincipals.desc=(Není potřeba pro vestavěný SSH server.) dashboard.resync_all_hooks=Znovu synchronizovat háčky před přijetím, aktualizace a po přijetí všech repozitářů. dashboard.reinit_missing_repos=Znovu inicializovat všechny chybějící repozitáře, pro které existují záznamy dashboard.sync_external_users=Synchronizovat externí uživatelská data @@ -2605,12 +2603,10 @@ packages.size=Velikost packages.published=Publikováno defaulthooks=Výchozí webové háčky -defaulthooks.desc=Webové háčky automaticky vytvářejí HTTP POST dotazy na server při určitých Gitea událostech. Webové háčky definované zde jsou výchozí a budou zkopírovány do všech nových repozitářů. Přečtěte si více v průvodci webovými háčky. defaulthooks.add_webhook=Přidat výchozí webový háček defaulthooks.update_webhook=Aktualizovat výchozí webový háček systemhooks=Systémové webové háčky -systemhooks.desc=Webové háčky automaticky vytvářejí HTTP POST dotazy na server při určitých Gitea událostech. Webové háčky definované zde budou vykonány na všech repozitářích systému, proto prosím zvažte jakékoli důsledky, které to může mít na výkon. Přečtěte si více v průvodci webovými háčky. systemhooks.add_webhook=Přidat systémový webový háček systemhooks.update_webhook=Aktualizovat systémový webový háček @@ -2713,7 +2709,6 @@ auths.tip.google_plus=Získejte klientské pověření OAuth2 z Google API konzo auths.tip.openid_connect=Použijte OpenID URL pro objevování spojení (/.well-known/openid-configuration) k nastavení koncových bodů auths.tip.twitter=Jděte na https://dev.twitter.com/apps, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená auths.tip.discord=Registrujte novou aplikaci na https://discordapp.com/developers/applications/me -auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://docs.gitea.io/en-us/oauth2-provider/ auths.tip.yandex=Vytvořte novou aplikaci na https://oauth.yandex.com/client/new. Vyberte následující oprávnění z „Yandex.Passport API“ sekce: „Přístup k e-mailové adrese“, „Přístup k uživatelskému avataru“ a „Přístup k uživatelskému jménu, jménu a příjmení, pohlaví“ auths.tip.mastodon=Vložte vlastní URL instance pro mastodon, kterou se chcete autentizovat (nebo použijte výchozí) auths.edit=Upravit zdroj ověřování @@ -2856,7 +2851,6 @@ config.disabled_logger=Zakázané config.access_log_mode=Režim logování přístupu config.xorm_log_sql=Logovat SQL -config.get_setting_failed=Získání nastavení %s se nezdařilo config.set_setting_failed=Nastavení %s se nezdařilo @@ -2885,8 +2879,6 @@ monitor.queue.exemplar=Typ vzoru monitor.queue.numberworkers=Počet workerů monitor.queue.maxnumberworkers=Maximální počet workerů monitor.queue.numberinqueue=Číslo ve frontě -monitor.queue.review=Konfigurace posouzení -monitor.queue.review_add=Posoudit/přidat workery monitor.queue.settings.title=Nastavení fondu monitor.queue.settings.maxnumberworkers=Maximální počet workerů monitor.queue.settings.maxnumberworkers.placeholder=V současné době %[1]d diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index d9773c0f5e..f970fdb666 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -97,6 +97,7 @@ locked=Gesperrt copy=Kopieren copy_url=URL kopieren +copy_hash=Hash kopieren copy_content=Inhalt kopieren copy_branch=Branchenname kopieren copy_success=Kopiert! @@ -112,6 +113,7 @@ step2=Schritt 2: error=Fehler error404=Die Seite, die Du versuchst aufzurufen, existiert nicht oder Du bist nicht berechtigt, diese anzusehen. +go_back=Zurück never=Niemals unknown=Unbekannt @@ -123,6 +125,8 @@ unpin=Loslösen artifacts=Artefakte +archived=Archiviert + concept_system_global=Global concept_user_individual=Individuum concept_code_repository=Repository @@ -181,7 +185,7 @@ network_error=Netzwerkfehler [startpage] app_desc=Ein einfacher, selbst gehosteter Git-Service install=Einfach zu installieren -install_desc=Starte einfach die Anwendung für deine Plattform. Oder nutze Docker. Es existieren auch paketierte Versionen. +install_desc=Starte einfach die Anwendung für deine Plattform oder nutze Docker. Es existieren auch paketierte Versionen. platform=Plattformübergreifend platform_desc=Gitea läuft überall, wo Go kompiliert: Windows, macOS, Linux, ARM, etc. Wähle das System, das dir am meisten gefällt! lightweight=Leichtgewicht @@ -370,6 +374,7 @@ reset_password_mail_sent_prompt=Eine Bestätigungs-E-Mail wurde an %s ges active_your_account=Aktiviere dein Konto account_activated=Konto wurde aktiviert prohibit_login=Anmelden verboten +prohibit_login_desc=Die Anmeldung mit diesem Konto ist nicht gestattet. Bitte kontaktiere den Administrator. resent_limit_prompt=Du hast bereits eine Aktivierungs-E-Mail angefordert. Bitte warte 3 Minuten und probiere es dann nochmal. has_unconfirmed_mail=Hallo %s, du hast eine unbestätigte E-Mail-Adresse (%s). Wenn du keine Bestätigungs-E-Mail erhalten hast oder eine neue senden möchtest, klicke bitte auf den folgenden Button. resend_mail=Aktivierungs-E-Mail erneut verschicken @@ -377,8 +382,10 @@ email_not_associate=Diese E-Mail-Adresse ist mit keinem Konto verknüpft. send_reset_mail=Wiederherstellungs-E-Mail senden reset_password=Kontowiederherstellung invalid_code=Dein Bestätigungs-Code ist ungültig oder abgelaufen. +invalid_code_forgot_password=Dein Bestätigungscode ist ungültig oder abgelaufen. Klicke hier, um eine neue Sitzung zu starten. invalid_password=Ihr Passwort stimmt nicht mit dem Passwort überein, das zur Erstellung des Kontos verwendet wurde. reset_password_helper=Konto wiederherstellen +reset_password_wrong_user=Du bist angemeldet als %s, aber der Link zur Kontowiederherstellung ist für %s password_too_short=Das Passwort muss mindestens %d Zeichen lang sein. non_local_account=Benutzer, die nicht von Gitea verwaltet werden können ihre Passwörter nicht über das Web Interface ändern. verify=Verifizieren @@ -403,6 +410,7 @@ openid_connect_title=Mit bestehendem Konto verbinden openid_connect_desc=Die gewählte OpenID-URI ist unbekannt. Ordne sie hier einem neuen Account zu. openid_register_title=Neues Konto einrichten openid_register_desc=Die gewählte OpenID-URI ist unbekannt. Ordne sie hier einem neuen Account zu. +openid_signin_desc=Gib deine OpenID-URI ein, zum Beispiel alice.openid.example.org oder https://openid.example.org/alice. disable_forgot_password_mail=Die Kontowiederherstellung ist deaktiviert, da keine E-Mail eingerichtet ist. Bitte kontaktiere den zuständigen Administrator. disable_forgot_password_mail_admin=Die Kontowiederherstellung ist nur verfügbar, wenn eine E-Mail eingerichtet wurde. Bitte richte eine E-Mail Adresse ein, um die Kontowiederherstellung freizuschalten. email_domain_blacklisted=Du kannst dich nicht mit deiner E-Mail-Adresse registrieren. @@ -412,7 +420,9 @@ authorize_application_created_by=Diese Anwendung wurde von %s erstellt. authorize_application_description=Wenn du diese Anwendung autorisierst, wird sie die Berechtigung erhalten, alle Informationen zu deinem Account zu bearbeiten oder zu lesen. Dies beinhaltet auch private Repositories und Organisationen. authorize_title=`"%s" den Zugriff auf deinen Account gestatten?` authorization_failed=Autorisierung fehlgeschlagen +authorization_failed_desc=Die Autorisierung ist fehlgeschlagen, da wir eine ungültige Anfrage erkannt haben. Bitte kontaktiere den Betreuer der App, die du zu autorisieren versucht hast. sspi_auth_failed=SSPI-Authentifizierung fehlgeschlagen +password_pwned=Das von dir gewählte Passwort befindet sich auf einer List gestohlener Passwörter, die öffentlich verfügbar sind. Bitte versuche es erneut mit einem anderen Passwort und ziehe in Erwägung, auch anderswo deine Passwörter zu ändern. password_pwned_err=Anfrage an HaveIBeenPwned konnte nicht abgeschlossen werden [mail] @@ -427,6 +437,7 @@ activate_account.text_1=Hallo %[1]s, danke für deine Registrierung bei % activate_account.text_2=Bitte klicke innerhalb von %s auf folgenden Link, um dein Konto zu aktivieren: activate_email=Bestätige deine E-Mail-Adresse +activate_email.title=%s, bitte verifiziere deine E-Mail-Adresse activate_email.text=Bitte klicke innerhalb von %s auf folgenden Link, um dein Konto zu aktivieren: register_notify=Willkommen bei Gitea @@ -592,11 +603,11 @@ overview=Übersicht following=Folge ich follow=Folgen unfollow=Nicht mehr folgen -heatmap.loading=Heatmap wird geladen… user_bio=Biografie disabled_public_activity=Dieser Benutzer hat die öffentliche Sichtbarkeit der Aktivität deaktiviert. email_visibility.limited=Ihre E-Mail-Adresse ist für alle authentifizierten Benutzer sichtbar email_visibility.private=Deine E-Mail-Adresse ist nur für Dich und Administratoren sichtbar +show_on_map=Diesen Ort auf einer Karte anzeigen settings=Benutzereinstellungen form.name_reserved=Der Benutzername "%s" ist reserviert. @@ -619,9 +630,13 @@ delete=Konto löschen twofa=Zwei-Faktor-Authentifizierung account_link=Verknüpfte Benutzerkonten organization=Organisationen +uid=UID webauthn=Hardware-Sicherheitsschlüssel public_profile=Öffentliches Profil +biography_placeholder=Erzähle uns ein wenig über Dich selbst! (Du kannst Markdown verwenden) +location_placeholder=Teile Deinen ungefähren Standort mit anderen +profile_desc=Lege fest, wie dein Profil anderen Benutzern angezeigt wird. Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und webbasierte Git-Operationen verwendet. password_username_disabled=Benutzer, die nicht von Gitea verwaltet werden können ihren Benutzernamen nicht ändern. Bitte kontaktiere deinen Administrator für mehr Details. full_name=Vollständiger Name website=Webseite @@ -633,6 +648,8 @@ update_language_not_found=Sprache "%s" ist nicht verfügbar. update_language_success=Sprache wurde aktualisiert. update_profile_success=Dein Profil wurde aktualisiert. change_username=Dein Benutzername wurde geändert. +change_username_prompt=Hinweis: Das Ändern deines Benutzernamen ändert auch deine Account-URL. +change_username_redirect_prompt=Der alte Benutzername wird auf den neuen Benutzernamen weiterleiten, bis er erneut als Benutzername verwendet wird. continue=Weiter cancel=Abbrechen language=Sprache @@ -657,6 +674,7 @@ comment_type_group_project=Projekt comment_type_group_issue_ref=Issue-Referenz saved_successfully=Die Einstellungen wurden erfolgreich gespeichert. privacy=Datenschutz +keep_activity_private=Aktivität auf der Profilseite ausblenden keep_activity_private_popup=Macht die Aktivität nur für dich und die Administratoren sichtbar lookup_avatar_by_mail=Profilbild anhand der E-Mail-Addresse suchen @@ -666,7 +684,7 @@ choose_new_avatar=Neues Profilbild auswählen update_avatar=Profilbild aktualisieren delete_current_avatar=Aktuelles Profilbild löschen uploaded_avatar_not_a_image=Die hochgeladene Datei ist kein Bild. -uploaded_avatar_is_too_big=Die hochgeladene Datei hat die maximale Größe überschritten. +uploaded_avatar_is_too_big=Die hochgeladene Dateigröße (%d KiB) überschreitet die maximale Größe (%d KiB). update_avatar_success=Dein Profilbild wurde geändert. update_user_avatar_success=Der Avatar des Benutzers wurde aktualisiert. @@ -682,6 +700,7 @@ emails=E-Mail-Adressen manage_emails=E-Mail-Adressen verwalten manage_themes=Standard-Theme auswählen manage_openid=OpenID-Adressen verwalten +email_desc=Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und, sofern sie nicht versteckt ist, web-basierte Git-Operationen verwendet. theme_desc=Dies wird dein Standard-Theme auf der Seite sein. primary=Primär activated=Aktiviert @@ -689,6 +708,7 @@ requires_activation=Erfordert Aktivierung primary_email=Als primäre E-Mail-Adresse verwenden activate_email=Aktivierung senden activations_pending=Aktivierung ausstehend +can_not_add_email_activations_pending=Es gibt eine ausstehende Aktivierung, versuche es in ein paar Minuten erneut, wenn du eine neue E-Mail hinzufügen möchtest. delete_email=Löschen email_deletion=E-Mail-Adresse löschen email_deletion_desc=Die E-Mail-Adresse und die damit verbundenen Informationen werden von deinem Konto entfernt. Git-Commits von dieser E-Mail-Addresse bleiben unverändert. Fortfahren? @@ -707,6 +727,7 @@ add_email_success=Die neue E-Mail-Addresse wurde hinzugefügt. email_preference_set_success=E-Mail-Einstellungen wurden erfolgreich aktualisiert. add_openid_success=Die neue OpenID-Adresse wurde hinzugefügt. keep_email_private=E-Mail-Adresse verbergen +keep_email_private_popup=Dies wird Deine E-Mail-Adresse nicht nur in Deinem Profil ausblenden, sondern auch, wenn Du einen Pull Request erstellst oder eine Datei über das Web-Interface bearbeitest. Gepushte Commits werden nicht geändert. openid_desc=Mit OpenID kannst du dich über einen Drittanbieter authentifizieren. manage_ssh_keys=SSH-Schlüssel verwalten @@ -785,7 +806,9 @@ ssh_disabled=SSH ist deaktiviert ssh_signonly=SSH ist derzeit deaktiviert, sodass diese Schlüssel nur zur Commit-Signaturverifizierung verwendet werden. ssh_externally_managed=Dieser SSH-Schlüssel wird extern für diesen Benutzer verwaltet manage_social=Verknüpfte soziale Konten verwalten +social_desc=Diese sozialen Konten können verwendet werden, um sich bei deinem Konto anzumelden. Stelle sicher, dass du sie alle zuordnen kannst. unbind=Trennen +unbind_success=Das soziale Konto wurde erfolgreich entfernt. manage_access_token=Zugriffstokens verwalten generate_new_token=Neuen Token erzeugen @@ -806,6 +829,8 @@ permissions_access_all=Alle (öffentlich, privat und begrenzt) select_permissions=Berechtigungen auswählen permission_no_access=Kein Zugriff permission_read=Gelesen +permission_write=Lesen und Schreiben +access_token_desc=Ausgewählte Token-Berechtigungen beschränken die Authentifizierung auf die entsprechenden API-Routen. Lies die Dokumentation für mehr Informationen. at_least_one_permission=Du musst mindestens eine Berechtigung auswählen, um ein Token zu erstellen permissions_list=Berechtigungen: @@ -817,6 +842,8 @@ remove_oauth2_application_desc=Das Entfernen einer OAuth2-Anwendung wird den Zug remove_oauth2_application_success=Die Anwendung wurde gelöscht. create_oauth2_application=Neue OAuth2 Anwendung erstellen create_oauth2_application_button=Anwendung erstellen +create_oauth2_application_success=Du hast erfolgreich eine neue OAuth2-Anwendung erstellt. +update_oauth2_application_success=Du hast die OAuth2-Anwendung erfolgreich aktualisiert. oauth2_application_name=Name der Anwendung oauth2_confidential_client=Vertraulicher Client. Für Anwendungen aktivieren, die das Geheimnis sicher speichern, z. B. Webanwendungen. Wähle diese Option nicht für native Anwendungen für PCs und Mobilgeräte. oauth2_redirect_uris=URIs für die Weiterleitung. Bitte verwende eine neue Zeile für jede URI. @@ -825,19 +852,25 @@ oauth2_client_id=Client-ID oauth2_client_secret=Client-Secret oauth2_regenerate_secret=Secret neu generieren oauth2_regenerate_secret_hint=Secret verloren? +oauth2_client_secret_hint=Das Secret wird nach dem Verlassen oder Aktualisieren dieser Seite nicht mehr angezeigt. Bitte stelle sicher, dass du es gespeichert hast. oauth2_application_edit=Bearbeiten oauth2_application_create_description=OAuth2 Anwendungen geben deiner Drittanwendung Zugriff auf Benutzeraccounts dieser Gitea-Instanz. +oauth2_application_remove_description=Das Entfernen einer OAuth2-Anwendung hat zur Folge, dass diese nicht mehr auf autorisierte Benutzeraccounts auf dieser Instanz zugreifen kann. Möchtest Du fortfahren? +oauth2_application_locked=Wenn es in der Konfiguration aktiviert ist, registriert Gitea einige OAuth2-Anwendungen beim Starten vor. Um unerwartetes Verhalten zu verhindern, können diese weder bearbeitet noch entfernt werden. Weitere Informationen findest Du in der OAuth2-Dokumentation. authorized_oauth2_applications=Autorisierte OAuth2-Anwendungen +authorized_oauth2_applications_description=Den folgenden Drittanbieter-Apps hast Du Zugriff auf Deinen persönlichen Gitea-Account gewährt. Bitte widerrufe die Autorisierung für Apps, die Du nicht mehr nutzt. revoke_key=Widerrufen revoke_oauth2_grant=Autorisierung widerrufen revoke_oauth2_grant_description=Wenn du die Autorisierung widerrufst, kann die Anwendung nicht mehr auf deine Daten zugreifen. Bist du dir sicher? +revoke_oauth2_grant_success=Zugriff erfolgreich widerrufen. twofa_desc=Zwei-Faktor-Authentifizierung trägt zu einer höheren Accountsicherheit bei. twofa_is_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung eingeschaltet. twofa_not_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung momentan nicht eingeschaltet. twofa_disable=Zwei-Faktor-Authentifizierung deaktivieren twofa_scratch_token_regenerate=Neues Einmalpasswort erstellen +twofa_scratch_token_regenerated=Dein temporärer Token ist jetzt %s. Speichere ihn an einem sicheren Ort, er wird nie wieder angezeigt. twofa_enroll=Zwei-Faktor-Authentifizierung aktivieren twofa_disable_note=Du kannst die Zwei-Faktor-Authentifizierung auch wieder deaktivieren. twofa_disable_desc=Wenn du die Zwei-Faktor-Authentifizierung deaktivierst, wird die Sicherheit deines Kontos verringert. Fortfahren? @@ -864,8 +897,10 @@ remove_account_link=Verknüpften Account entfernen remove_account_link_desc=Wenn du den verknüpften Account entfernst, wirst du darüber nicht mehr auf deinen Gitea-Account zugreifen können. Fortfahren? remove_account_link_success=Der verknüpfte Account wurde entfernt. +hooks.desc=Webhooks hinzufügen, die für alle Repositories, die dir gehören, ausgelöst werden. orgs_none=Du bist kein Mitglied in einer Organisation. +repos_none=Du besitzt keine Repositories. delete_account=Konto löschen delete_prompt=Wenn du fortfährst, wird dein Account permanent gelöscht. Dies KANN NICHT rückgängig gemacht werden. @@ -884,9 +919,12 @@ visibility=Nutzer Sichtbarkeit visibility.public=Öffentlich visibility.public_tooltip=Für alle sichtbar visibility.limited=Begrenzt +visibility.limited_tooltip=Nur für authentifizierte Benutzer sichtbar visibility.private=Privat +visibility.private_tooltip=Sichtbar nur für Mitglieder von Organisationen, denen du beigetreten bist [repo] +new_repo_helper=Ein Repository enthält alle Projektdateien, einschließlich des Änderungsverlaufs. Schon woanders vorhanden? Migriere das Repository. owner=Besitzer owner_helper=Einige Organisationen könnten in der Dropdown-Liste nicht angezeigt werden, da die Anzahl an Repositories begrenzt ist. repo_name=Repository-Name @@ -898,6 +936,7 @@ template_helper=Repository zu einem Template machen template_description=Template-Repositories erlauben es Benutzern, neue Repositories mit den gleichen Verzeichnisstrukturen, Dateien und optionalen Einstellungen zu erstellen. visibility=Sichtbarkeit visibility_description=Nur der Besitzer oder Organisationsmitglieder mit entsprechender Berechtigung, werden in der Lage sein, es zu sehen. +visibility_helper=In privates Repository umwandeln visibility_helper_forced=Auf dieser Gitea-Instanz können nur private Repositories angelegt werden. visibility_fork_helper=(Eine Änderung dieses Wertes wirkt sich auf alle Forks aus) clone_helper=Benötigst du Hilfe beim Klonen? Öffne die Hilfe. @@ -906,6 +945,9 @@ fork_from=Fork von already_forked=Du hast bereits einen Fork von %s erstellt fork_to_different_account=Fork in ein anderes Konto erstellen fork_visibility_helper=Die Sichtbarkeit eines geforkten Repositories kann nicht geändert werden. +fork_branch=Branch, der zum Fork geklont werden soll +all_branches=Alle Branches +fork_no_valid_owners=Dieses Repository kann nicht geforkt werden, da keine gültigen Besitzer vorhanden sind. use_template=Dieses Template verwenden clone_in_vsc=In VS Code klonen download_zip=ZIP herunterladen @@ -934,6 +976,7 @@ trust_model_helper_collaborator_committer=Mitarbeiter+Committer: Vertraue Signat trust_model_helper_default=Standard: Verwende das Standardvertrauensmodell für diese Installation create_repo=Repository erstellen default_branch=Standardbranch +default_branch_label=Standard default_branch_helper=Der default Branch ist der Basisbranch für Pull-Requests und Commits. mirror_prune=Entfernen mirror_prune_desc=Entferne veraltete remote-tracking Referenzen @@ -942,6 +985,8 @@ mirror_interval_invalid=Das Spiegel-Intervall ist ungültig. mirror_sync_on_commit=Synchronisieren, wenn Commits gepusht wurden mirror_address=Klonen via URL mirror_address_desc=Gib alle erforderlichen Anmeldedaten im Abschnitt "Authentifizierung" ein. +mirror_address_url_invalid=Die angegebene URL ist ungültig. Achte darauf, alle URL-Komponenten korrekt zu maskieren. +mirror_address_protocol_invalid=Die angegebene URL ist ungültig. Nur URLs beginnend mit http(s):// oder git:// sind möglich. mirror_lfs=Großdatei-Speicher (LFS) mirror_lfs_desc=Mirroring von LFS-Dateien aktivieren. mirror_lfs_endpoint=LFS-Endpunkt @@ -967,12 +1012,20 @@ delete_preexisting=Vorhandene Dateien löschen delete_preexisting_content=Dateien in %s löschen delete_preexisting_success=Nicht übernommene Dateien in %s gelöscht blame_prior=Blame vor dieser Änderung anzeigen +blame.ignore_revs=Revisionen in .git-blame-ignore-revs werden ignoriert. Klicke hier, um das zu umgehen und die normale Blame-Ansicht zu sehen. +blame.ignore_revs.failed=Fehler beim Ignorieren der Revisionen in .git-blame-ignore-revs. author_search_tooltip=Zeigt maximal 30 Benutzer +tree_path_not_found_commit=Pfad %[1]s existiert nicht in Commit%[2]s +tree_path_not_found_branch=Pfad %[1]s existiert nicht in Branch %[2]s +tree_path_not_found_tag=Pfad %[1]s existiert nicht in Tag %[2]s + transfer.accept=Übertragung Akzeptieren transfer.accept_desc=`Übertragung nach "%s"` transfer.reject=Übertragung Ablehnen transfer.reject_desc=Übertragung nach "%s " abbrechen +transfer.no_permission_to_accept=Du hast keine Berechtigung, diesen Transfer anzunehmen. +transfer.no_permission_to_reject=Du hast keine Berechtigung, diesen Transfer abzulehnen. desc.private=Privat desc.public=Öffentlich @@ -993,6 +1046,8 @@ template.issue_labels=Issue Label template.one_item=Es muss mindestens ein Template ausgewählt werden template.invalid=Es muss ein Template-Repository ausgewählt werden +archive.title=Dieses Repository ist archiviert. Du kannst Dateien ansehen und es klonen, kannst aber nicht pushen oder Issues/Pull-Requests öffnen. +archive.title_date=Dieses Repository wurde am %s archiviert. Du kannst Dateien ansehen und es klonen, aber nicht pushen oder Issues/Pull-Requests öffnen. archive.issue.nocomment=Dieses Repo ist archiviert. Du kannst Issues nicht kommentieren. archive.pull.nocomment=Dieses Repo ist archiviert. Du kannst Pull-Requests nicht kommentieren. @@ -1009,6 +1064,7 @@ migrate_options_lfs=LFS-Dateien migrieren migrate_options_lfs_endpoint.label=LFS-Endpunkt migrate_options_lfs_endpoint.description=Migration wird versuchen, über den entfernten Git-Server den LFS-Server zu bestimmen. Du kannst auch einen eigenen Endpunkt angeben, wenn die LFS-Dateien woanders gespeichert werden. migrate_options_lfs_endpoint.description.local=Ein lokaler Serverpfad wird ebenfalls unterstützt. +migrate_options_lfs_endpoint.placeholder=Wenn leer gelassen, wird der Endpunkt von der Clone-URL abgeleitet migrate_items=Migrationselemente migrate_items_wiki=Wiki migrate_items_milestones=Meilensteine @@ -1111,6 +1167,10 @@ file_view_rendered=Ansicht rendern file_view_raw=Originalformat anzeigen file_permalink=Permalink file_too_large=Die Datei ist zu groß zum Anzeigen. +invisible_runes_header=`Diese Datei enthält unsichtbare Unicode-Zeichen` +invisible_runes_description=`Diese Datei enthält unsichtbare Unicode-Zeichen, die für Menschen nicht unterscheidbar sind, aber von einem Computer unterschiedlich verarbeitet werden können. Wenn du glaubst, dass das absichtlich so ist, kannst du diese Warnung ignorieren. Benutze den „Escape“-Button, um versteckte Zeichen anzuzeigen.` +ambiguous_runes_header=`Diese Datei enthält mehrdeutige Unicode-Zeichen` +ambiguous_runes_description=`Diese Datei enthält Unicode-Zeichen, die mit anderen Zeichen verwechselt werden können. Wenn du glaubst, dass das absichtlich so ist, kannst du diese Warnung ignorieren. Benutze den „Escape“-Button, um versteckte Zeichen anzuzeigen.` invisible_runes_line=`Diese Zeile enthält unsichtbare Unicode-Zeichen` ambiguous_runes_line=`Diese Zeile enthält mehrdeutige Unicode-Zeichen` ambiguous_character=`%[1]c [U+%04[1]X] kann mit %[2]c [U+%04[2]X] verwechselt werden` @@ -1123,11 +1183,15 @@ video_not_supported_in_browser=Dein Browser unterstützt das HTML5 'video'-Tag n audio_not_supported_in_browser=Dein Browser unterstützt den HTML5 'audio'-Tag nicht. stored_lfs=Gespeichert mit Git LFS symbolic_link=Softlink +executable_file=Ausführbare Datei commit_graph=Commit graph commit_graph.select=Branches auswählen commit_graph.hide_pr_refs=Pull-Requests ausblenden commit_graph.monochrome=Monochrom commit_graph.color=Farbe +commit.contained_in=Dieser Commit ist enthalten in: +commit.contained_in_default_branch=Dieser Commit ist im Standard-Branch enthalten +commit.load_referencing_branches_and_tags=Lade Branches und Tags, die diesen Commit referenzieren blame=Blame download_file=Datei herunterladen normal_view=Normale Ansicht @@ -1220,6 +1284,7 @@ commits.signed_by_untrusted_user=Signiert von nicht vertrauenswürdigen Benutzer commits.signed_by_untrusted_user_unmatched=Signiert von nicht vertrauenswürdigen Benutzern, der nicht mit dem Committer übereinstimmt commits.gpg_key_id=GPG-Schlüssel-ID commits.ssh_key_fingerprint=SSH-Key-Fingerabdruck +commits.view_path=An diesem Punkt im Verlauf anzeigen commit.operations=Operationen commit.revert=Zurücksetzen @@ -1229,6 +1294,11 @@ commit.cherry-pick=Cherry-Pick commit.cherry-pick-header=Cherry-Picke: %s commit.cherry-pick-content=Branch auswählen, auf dem Cherry-Picked werden soll: +commitstatus.error=Fehler +commitstatus.failure=Fehler +commitstatus.pending=Ausstehend +commitstatus.success=Erfolg + ext_issues=Zugriff auf Externe Issues ext_issues.desc=Link zu externem Issuetracker. @@ -1376,6 +1446,7 @@ issues.filter_sort.moststars=Meiste Favoriten issues.filter_sort.feweststars=Wenigste Favoriten issues.filter_sort.mostforks=Meiste Forks issues.filter_sort.fewestforks=Wenigste Forks +issues.keyword_search_unavailable=Zurzeit ist die Stichwort-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. issues.action_open=Öffnen issues.action_close=Schließen issues.action_label=Label @@ -1423,9 +1494,18 @@ issues.ref_reopening_from=`hat auf einen Pull Request %[4]s verw issues.ref_closed_from=`hat dieses Issue %[4]s geschlossen %[2]s` issues.ref_reopened_from=`hat dieses Issue %[4]s %[2]s wieder geöffnet` issues.ref_from=`von %[1]s` -issues.poster=Ersteller -issues.collaborator=Mitarbeiter -issues.owner=Besitzer +issues.author=Autor +issues.author_helper=Dieser Benutzer ist der Autor. +issues.role.owner=Besitzer +issues.role.owner_helper=Dieser Benutzer ist der Besitzer dieses Repositorys. +issues.role.member=Mitglied +issues.role.member_helper=Dieser Benutzer ist Mitglied der Organisation, die dieses Repository besitzt. +issues.role.collaborator=Mitarbeiter +issues.role.collaborator_helper=Dieser Benutzer wurde zur Zusammenarbeit am Repository eingeladen. +issues.role.first_time_contributor=Erstmaliger Mitwirkender +issues.role.first_time_contributor_helper=Dies ist der erste Beitrag dieses Benutzers zum Repository. +issues.role.contributor=Mitwirkender +issues.role.contributor_helper=Dieser Benutzer hat schon zuvor zu dem Repository beigetragen. issues.re_request_review=Review erneut anfordern issues.is_stale=Seit diesem Review gab es Änderungen an diesem PR issues.remove_request_review=Review-Anfrage entfernen @@ -1440,6 +1520,9 @@ issues.label_title=Labelname issues.label_description=Labelbeschreibung issues.label_color=Labelfarbe issues.label_exclusive=Exklusiv +issues.label_archive=Label archivieren +issues.label_archived_filter=Archivierte Labels anzeigen +issues.label_archive_tooltip=Archivierte Labels werden bei der Suche nach Label standardmäßig von den Vorschlägen ausgeschlossen. issues.label_exclusive_desc=Nenne das Label Bereich/Element um es gegenseitig ausschließend mit anderen Bereich/ Labels zu machen. issues.label_exclusive_warning=Alle im Konflikt stehenden Labels werden beim Bearbeiten der Labels eines Issues oder eines Pull-Requests entfernt. issues.label_count=%d Label @@ -1494,6 +1577,7 @@ issues.tracking_already_started=`Du hast die Zeiterfassung bereits in %[2]s#%[3]d` @@ -1644,6 +1737,12 @@ pulls.is_empty=Die Änderungen an diesem Branch sind bereits auf dem Zielbranch. pulls.required_status_check_failed=Einige erforderliche Prüfungen waren nicht erfolgreich. pulls.required_status_check_missing=Einige erforderliche Prüfungen fehlen. pulls.required_status_check_administrator=Als Administrator kannst du diesen Pull-Request weiterhin mergen. +pulls.blocked_by_approvals=Dieser Pull-Request hat noch nicht genügend Zustimmungen. %d von %d Zustimmungen erteilt. +pulls.blocked_by_rejection=Dieser Pull-Request hat Änderungen, die von einem offiziellen Reviewer angefragt wurden. +pulls.blocked_by_official_review_requests=Dieser Pull Request hat offizielle Review-Anfragen. +pulls.blocked_by_outdated_branch=Dieser Pull Request ist blockiert, da er veraltet ist. +pulls.blocked_by_changed_protected_files_1=Dieser Pull Request ist blockiert, weil er eine geschützte Datei ändert: +pulls.blocked_by_changed_protected_files_n=Dieser Pull Request ist blockiert, weil er geschützte Dateien ändert: pulls.can_auto_merge_desc=Dieser Pull-Request kann automatisch gemergt werden. pulls.cannot_auto_merge_desc=Dieser Pull-Request kann nicht automatisch gemergt werden, da es Konflikte gibt. pulls.cannot_auto_merge_helper=Bitte manuell mergen, um die Konflikte zu beheben. @@ -1678,6 +1777,7 @@ pulls.rebase_conflict_summary=Fehlermeldung pulls.unrelated_histories=Merge fehlgeschlagen: Der Head des Merges und die Basis haben keinen gemeinsamen Verlauf. Hinweis: Versuche eine andere Strategie pulls.merge_out_of_date=Merge fehlgeschlagen: Während des Mergens wurde die Basis aktualisiert. Hinweis: Versuche es erneut. pulls.head_out_of_date=Mergen fehlgeschlagen: Der Head wurde aktualisiert während der Merge erstellt wurde. Tipp: Versuche es erneut. +pulls.has_merged=Fehler: Der Pull-Request wurde gemerged, du kannst den Zielbranch nicht wieder mergen oder ändern. pulls.push_rejected=Mergen fehlgeschlagen: Der Push wurde abgelehnt. Überprüfe die Git Hooks für dieses Repository. pulls.push_rejected_summary=Vollständige Ablehnungsmeldung pulls.push_rejected_no_message=Mergen fehlgeschlagen: Der Push wurde abgelehnt, aber es gab keine Fehlermeldung.
Überprüfe die Git Hooks für dieses Repository @@ -1718,6 +1818,7 @@ pulls.auto_merge_canceled_schedule_comment=`hat den Automerge für diesen Pull-R pulls.delete.title=Diesen Pull-Request löschen? pulls.delete.text=Willst du diesen Pull-Request wirklich löschen? (Dies wird den Inhalt unwiderruflich löschen. Überlege, ob du ihn nicht lieber schließen willst, um ihn zu archivieren) +pulls.recently_pushed_new_branches=Du hast auf den Branch %[1]s %[2]s gepusht pull.deleted_branch=(gelöscht):%s @@ -1727,6 +1828,7 @@ milestones.update_ago=%s aktualisiert milestones.no_due_date=Kein Fälligkeitsdatum milestones.open=Öffnen milestones.close=Schließen +milestones.new_subheader=Benutze Meilensteine, um Issues zu organisieren und den Fortschritt darzustellen. milestones.completeness=%d%% abgeschlossen milestones.create=Meilenstein erstellen milestones.title=Titel @@ -1743,13 +1845,26 @@ milestones.edit_success=Meilenstein "%s" wurde aktualisiert. milestones.deletion=Meilenstein löschen milestones.deletion_desc=Das Löschen des Meilensteins entfernt ihn von allen Issues. Fortfahren? milestones.deletion_success=Der Meilenstein wurde gelöscht. -milestones.filter_sort.closest_due_date=Nächster Stichtag -milestones.filter_sort.furthest_due_date=Fernster Stichtag +milestones.filter_sort.earliest_due_data=Frühestes Fälligkeitsdatum +milestones.filter_sort.latest_due_date=Spätestes Fälligkeitsdatum milestones.filter_sort.least_complete=Am wenigsten vollständig milestones.filter_sort.most_complete=Vollständigste milestones.filter_sort.most_issues=Meiste Issues milestones.filter_sort.least_issues=Wenigste Issues +signing.will_sign=Dieser Commit wird mit dem Key "%s" signiert werden. +signing.wont_sign.error=Es gab einen Fehler bei der Prüfung, ob der Commit signiert werden kann. +signing.wont_sign.nokey=Es ist kein Schlüssel zum Signieren dieses Commits verfügbar. +signing.wont_sign.never=Commits werden nie signiert. +signing.wont_sign.always=Commits werden immer signiert. +signing.wont_sign.pubkey=Der Commit wird nicht signiert, da du keinen öffentlichen Schlüssel mit deinem Account verknüpft hast. +signing.wont_sign.twofa=Du musst Zwei-Faktor-Authentifizierung aktivieren, damit Commits signiert werden. +signing.wont_sign.parentsigned=Der Commit wird nicht signiert werden, da der vorherige Commit nicht signiert ist. +signing.wont_sign.basesigned=Der Merge Commit wird nicht signiert werden, da der Basis-Commit nicht signiert ist. +signing.wont_sign.headsigned=Der Merge Commit wird nicht signiert werden, da der Head-Commit nicht signiert ist. +signing.wont_sign.commitssigned=Der Merge Commit wird nicht signiert werden, da alle zugehörigen Commits nicht signiert sind. +signing.wont_sign.approved=Der Merge Commit wird nicht signiert werden, da der Pull Request nicht genehmigt wurde. +signing.wont_sign.not_signed_in=Du bist nicht eingeloggt. ext_wiki=Zugriff auf externes Wiki ext_wiki.desc=Verweis auf externes Wiki. @@ -1879,7 +1994,9 @@ settings.mirror_settings.docs.disabled_push_mirror.info=Push-Mirrors wurden von settings.mirror_settings.docs.no_new_mirrors=Dein Repository spiegelt Änderungen von oder zu einem anderen Repository. Bitte beachte, dass du gerade keine neuen Mirror anlegen kannst. settings.mirror_settings.docs.can_still_use=Obwohl du existierende Mirrors gerade nicht bearbeiten oder neu anlegen kannst, sind bestehende Mirrors weiterhin nutzbar. settings.mirror_settings.docs.pull_mirror_instructions=Um einen Pull-Mirror einzurichten, konsultiere bitte: +settings.mirror_settings.docs.more_information_if_disabled=Hier kannst du mehr über Push- und Pull-Mirrors erfahren: settings.mirror_settings.docs.doc_link_title=Wie spiegele ich Repositories? +settings.mirror_settings.docs.doc_link_pull_section=den Abschnitt "Von einem entfernten Repository pullen" in der Dokumentation. settings.mirror_settings.docs.pulling_remote_title=Aus einem Remote-Repository pullen settings.mirror_settings.mirrored_repository=Gespiegeltes Repository settings.mirror_settings.direction=Richtung @@ -1889,9 +2006,11 @@ settings.mirror_settings.last_update=Letzte Aktualisierung settings.mirror_settings.push_mirror.none=Keine Push-Mirrors konfiguriert settings.mirror_settings.push_mirror.remote_url=URL zum Git-Remote-Repository settings.mirror_settings.push_mirror.add=Push-Mirror hinzufügen +settings.mirror_settings.push_mirror.edit_sync_time=Mirror-Sync-Intervall anpassen settings.sync_mirror=Jetzt synchronisieren -settings.mirror_sync_in_progress=Mirror-Synchronisierung wird zurzeit ausgeführt. Komm in ein paar Minuten zurück. +settings.pull_mirror_sync_in_progress=Aktuell werden Änderungen von %s gepullt. +settings.push_mirror_sync_in_progress=Aktuell werden Änderungen auf %s gepusht. settings.site=Webseite settings.update_settings=Einstellungen speichern settings.update_mirror_settings=Mirror-Einstellungen aktualisieren @@ -1958,6 +2077,7 @@ settings.transfer.rejected=Repository-Übertragung wurde abgelehnt. settings.transfer.success=Repository-Übertragung war erfolgreich. settings.transfer_abort=Übertragung abbrechen settings.transfer_abort_invalid=Du kannst nur eingeleitete Repository-Übertragung abbrechen. +settings.transfer_abort_success=Die Repository-Übertragung zu %s wurde abgebrochen. settings.transfer_desc=Übertrage dieses Repository auf einen anderen Benutzer oder eine Organisation, in der du Admin-Rechte hast. settings.transfer_form_title=Gib den Repository-Namen zur Bestätigung ein: settings.transfer_in_progress=Es gibt derzeit eine laufende Übertragung. Bitte brich diese ab, wenn du dieses Repository an einen anderen Benutzer übertragen möchtest. @@ -2024,12 +2144,14 @@ settings.webhook_deletion_desc=Das Entfernen eines Webhooks löscht seine Einste settings.webhook_deletion_success=Webhook wurde entfernt. settings.webhook.test_delivery=Senden testen settings.webhook.test_delivery_desc=Teste diesen Webhook mit einem Fake-Event. +settings.webhook.test_delivery_desc_disabled=Um diesen Webhook mit einem Fake-Event zu testen, aktiviere ihn. settings.webhook.request=Anfrage settings.webhook.response=Antwort settings.webhook.headers=Kopfzeilen settings.webhook.payload=Inhalt settings.webhook.body=Inhalt settings.webhook.replay.description=Diesen Webhook wiederholen. +settings.webhook.replay.description_disabled=Um diesen Webhook wiederzugeben, aktiviere ihn. settings.webhook.delivery.success=Ein Event wurde zur Sendungs-Warteschlange hinzugefügt. Es kann ein paar Sekunden dauern, bevor es im Verlauf erscheint. settings.githooks_desc=Git-Hooks werden von Git selbst bereitgestellt. Du kannst die Dateien der unterstützten Hooks in der Liste unten bearbeiten, um eigene Operationen einzubinden. settings.githook_edit_desc=Wenn ein Hook nicht aktiv ist, wird der Standardinhalt benutzt. Lasse den Inhalt leer, um den Hook zu deaktivieren. @@ -2189,6 +2311,7 @@ settings.dismiss_stale_approvals_desc=Wenn neue Commits gepusht werden, die den settings.require_signed_commits=Signierte Commits erforderlich settings.require_signed_commits_desc=Pushes auf diesen Branch ablehnen, wenn Commits nicht signiert oder nicht überprüfbar sind. settings.protect_branch_name_pattern=Muster für geschützte Branchnamen +settings.protect_branch_name_pattern_desc=Geschützte Branch-Namensmuster. Lies die Dokumentation für die Muster-Syntax. Beispiele: main, release/** settings.protect_patterns=Muster settings.protect_protected_file_patterns=Geschützte Dateimuster (durch Semikolon ';' getrennt): settings.protect_protected_file_patterns_desc=Geschützte Dateien dürfen nicht direkt geändert werden, auch wenn der Benutzer Rechte hat, Dateien in diesem Branch hinzuzufügen, zu bearbeiten oder zu löschen. Mehrere Muster können mit Semikolon (';') getrennt werden. Siehe github.com/gobwas/glob Dokumentation zur Mustersyntax. Beispiele: .drone.yml, /docs/**/*.txt. @@ -2225,19 +2348,26 @@ settings.tags.protection.allowed.teams=Erlaubte Teams settings.tags.protection.allowed.noone=Niemand settings.tags.protection.create=Tag schützen settings.tags.protection.none=Es gibt keine geschützten Tags. -settings.tags.protection.pattern.description=Du kannst einen einzigen Namen oder ein globales Schema oder einen regulären Ausdruck verwenden, um mehrere Tags zu schützen. Mehr dazu im geschützte Tags Guide (Englisch). +settings.tags.protection.pattern.description=Du kannst einen einzigen Namen oder ein globales Schema oder einen regulären Ausdruck verwenden, um mehrere Tags zu schützen. Mehr dazu im Guide für geschützte Tags (Englisch). settings.bot_token=Bot-Token settings.chat_id=Chat-ID +settings.thread_id=Thread-ID settings.matrix.homeserver_url=Homeserver-URL settings.matrix.room_id=Raum-ID settings.matrix.message_type=Nachrichtentyp settings.archive.button=Repo archivieren settings.archive.header=Dieses Repo archivieren +settings.archive.text=Durch das Archivieren wird ein Repo vollständig schreibgeschützt. Es wird vom Dashboard versteckt. Niemand (nicht einmal du!) wird in der Lage sein, neue Commits zu erstellen oder Issues oder Pull-Requests zu öffnen. settings.archive.success=Das Repo wurde erfolgreich archiviert. settings.archive.error=Beim Versuch, das Repository zu archivieren, ist ein Fehler aufgetreten. Weitere Details finden sich im Log. settings.archive.error_ismirror=Du kannst keinen Repo-Mirror archivieren. settings.archive.branchsettings_unavailable=Branch-Einstellungen sind nicht verfügbar wenn das Repo archiviert ist. settings.archive.tagsettings_unavailable=Tag Einstellungen sind nicht verfügbar, wenn das Repo archiviert wurde. +settings.unarchive.button=Archivieren rückgängig machen +settings.unarchive.header=Archivieren dieses Repositories rückgängig machen +settings.unarchive.text=Durch das Aufheben der Archivierung kann das Repo wieder Commits und Pushes sowie neue Issues und Pull-Requests empfangen. +settings.unarchive.success=Die Archivierung des Repos wurde erfolgreich wieder rückgängig gemacht. +settings.unarchive.error=Beim Versuch, die Archivierung des Repos aufzuheben, ist ein Fehler aufgetreten. Weitere Details finden sich im Log. settings.update_avatar_success=Der Repository-Avatar wurde aktualisiert. settings.lfs=LFS settings.lfs_filelist=LFS-Dateien, die in diesem Repository gespeichert sind @@ -2304,6 +2434,7 @@ diff.show_more=Mehr anzeigen diff.load=Diff laden diff.generated=generiert diff.vendored=vendored +diff.comment.add_line_comment=Einzelnen Kommentar hinzufügen diff.comment.placeholder=Kommentieren... diff.comment.markdown_info=Styling mit Markdown wird unterstützt. diff.comment.add_single_comment=Einzelnen Kommentar hinzufügen @@ -2360,6 +2491,7 @@ release.edit_release=Release aktualisieren release.delete_release=Release löschen release.delete_tag=Tag löschen release.deletion=Release löschen +release.deletion_desc=Beim Entfernen wird ein Release nur von Gitea gelöscht. Es betrifft weder den Git-Tag, noch den Inhalt des Repos oder seinen Änderungsverlauf. Fortfahren? release.deletion_success=Das Release wurde gelöscht. release.deletion_tag_desc=Löscht dieses Tag aus dem Projektarchiv. Repository-Inhalt und Verlauf bleiben unverändert. Fortfahren? release.deletion_tag_success=Der Tag wurde gelöscht. @@ -2375,11 +2507,12 @@ release.releases_for=Releases für %s release.tags_for=Tags für %s branch.name=Branchname -branch.search=Branches durchsuchen +branch.search=Branch suchen branch.already_exists=Ein Branch mit dem Namen "%s" existiert bereits. branch.delete_head=Löschen branch.delete=Branch "%s" löschen branch.delete_html=Branch löschen +branch.delete_desc=Das Löschen eines Branches ist permanent. Obwohl der Branch für eine kurze Zeit weiter existieren könnte, kann diese Aktion in den meisten Fällen NICHT rückgängig gemacht werden. Fortfahren? branch.deletion_success=Branch "%s" wurde gelöscht. branch.deletion_failed=Branch "%s" konnte nicht gelöscht werden. branch.delete_branch_has_new_commits=Der Branch "%s" kann nicht gelöscht werden, da seit dem letzten Merge neue Commits hinzugefügt wurden. @@ -2419,6 +2552,7 @@ tag.create_success=Tag "%s" wurde erstellt. topic.manage_topics=Themen verwalten topic.done=Fertig topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen +topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) und Punkte ('.') enthalten und bis zu 35 Zeichen lang sein. Nur Kleinbuchstaben sind zulässig. find_file.go_to_file=Datei suchen find_file.no_matching=Keine passende Datei gefunden @@ -2457,6 +2591,7 @@ form.create_org_not_allowed=Du bist nicht berechtigt, eine Organisation zu erste settings=Einstellungen settings.options=Organisation settings.full_name=Vollständiger Name +settings.email=Kontakt-E-Mail-Adresse settings.website=Webseite settings.location=Standort settings.permission=Berechtigungen @@ -2470,6 +2605,7 @@ settings.visibility.private_shortname=Privat settings.update_settings=Einstellungen speichern settings.update_setting_success=Organisationseinstellungen wurden aktualisiert. +settings.change_orgname_prompt=Hinweis: Das Ändern des Organisationsnamens wird auch die URL deiner Organisation ändern und den alten Namen freigeben. settings.change_orgname_redirect_prompt=Der alte Name wird weiterleiten, bis er wieder beansprucht wird. settings.update_avatar_success=Der Organisationsavatar wurde aktualisiert. settings.delete=Organisation löschen @@ -2545,15 +2681,19 @@ teams.all_repositories_helper=Team hat Zugriff auf alle Repositories. Wenn dies teams.all_repositories_read_permission_desc=Dieses Team gewährt Lese-Zugriff auf Repositories: Mitglieder können Repositories ansehen und klonen. teams.all_repositories_write_permission_desc=Dieses Team gewährt Schreib-Zugriff auf alle Repositories: Mitglieder können Repositories lesen und auf sie pushen. teams.all_repositories_admin_permission_desc=Dieses Team gewährt Administrator-Zugriff auf alle Repositories: Mitglieder können Repositories lesen, auf sie pushen und Mitwirkende zu Repositorys hinzufügen. +teams.invite.title=Du wurdest eingeladen, dem Team %s in der Organisation %s beizutreten. teams.invite.by=Von %s eingeladen teams.invite.description=Bitte klicke auf die folgende Schaltfläche, um dem Team beizutreten. [admin] dashboard=Dashboard +identity_access=Identität & Zugriff users=Benutzerkonten organizations=Organisationen +assets=Code-Assets repositories=Repositories hooks=Webhooks +integrations=Integrationen authentication=Authentifizierungsquellen emails=Benutzer E-Mails config=Konfiguration @@ -2562,6 +2702,7 @@ monitor=Monitoring first_page=Erste last_page=Letzte total=Gesamt: %d +settings=Administratoreinstellungen dashboard.new_version_hint=Gitea %s ist jetzt verfügbar, deine derzeitige Version ist %s. Weitere Details findest du im Blog. dashboard.statistic=Übersicht @@ -2574,11 +2715,13 @@ dashboard.clean_unbind_oauth=Nicht verbundene OAuth-Verbindungen löschen dashboard.clean_unbind_oauth_success=Alle unverbundene OAuth-Verbindungen wurden gelöscht. dashboard.task.started=Aufgabe gestartet: %[1]s dashboard.task.process=Aufgabe: %[1]s +dashboard.task.cancelled=Aufgabe: %[1]s abgebrochen: %[3]s dashboard.task.error=Fehler in Aufgabe: %[1]s: %[3]s dashboard.task.finished=Aufgabe: %[1]s, gestartet von %[2]s, wurde beendet dashboard.task.unknown=Unbekannte Aufgabe: %[1]s dashboard.cron.started=Cron gestartet: %[1]s dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Cron: %[1]s abgebrochen: %[3]s dashboard.cron.error=Fehler in Cron: %s: %[3]s dashboard.cron.finished=Cron: %[1]s ist beendet dashboard.delete_inactive_accounts=Alle nicht aktivierten Konten löschen @@ -2588,6 +2731,7 @@ dashboard.delete_repo_archives.started=Löschen aller Repository-Archive gestart dashboard.delete_missing_repos=Alle Repository-Datensätze mit verloren gegangenen Git-Dateien löschen dashboard.delete_missing_repos.started=Alle Repositories löschen, die den Git-File-Task nicht gestartet haben. dashboard.delete_generated_repository_avatars=Generierte Repository-Avatare löschen +dashboard.sync_repo_branches=Fehlende Branches aus den Git-Daten in die Datenbank synchronisieren dashboard.update_mirrors=Mirrors aktualisieren dashboard.repo_health_check=Healthchecks für alle Repositories ausführen dashboard.check_repo_stats=Überprüfe alle Repository-Statistiken @@ -2596,14 +2740,13 @@ dashboard.deleted_branches_cleanup=Gelöschte Branches bereinigen dashboard.update_migration_poster_id=Migration Poster-IDs updaten dashboard.git_gc_repos=Garbage-Collection für alle Repositories ausführen dashboard.resync_all_sshkeys=Die Datei '.ssh/authorized_keys' mit Gitea SSH-Schlüsseln aktualisieren. -dashboard.resync_all_sshkeys.desc=(Nicht benötigt für den eingebauten SSH-Server.) dashboard.resync_all_sshprincipals=Aktualisiere die Datei '.ssh/authorized_principals' mit Gitea SSH Identitäten. -dashboard.resync_all_sshprincipals.desc=(Nicht benötigt für den eingebauten SSH-Server.) dashboard.resync_all_hooks=Die „pre-receive“-, „update“- und „post-receive“-Hooks für alle Repositories erneut synchronisieren. dashboard.reinit_missing_repos=Alle Git-Repositories neu einlesen, für die Einträge existieren dashboard.sync_external_users=Externe Benutzerdaten synchronisieren dashboard.cleanup_hook_task_table=Hook-Task-Tabelle bereinigen dashboard.cleanup_packages=Veraltete Pakete löschen +dashboard.cleanup_actions=Abgelaufene Logs und Artefakte von Actions bereinigen dashboard.server_uptime=Server-Uptime dashboard.current_goroutine=Aktuelle Goroutinen dashboard.current_memory_usage=Aktuelle Speichernutzung @@ -2641,6 +2784,9 @@ dashboard.gc_lfs=Garbage-Collection für LFS Meta-Objekte ausführen dashboard.stop_zombie_tasks=Zombie-Aufgaben stoppen dashboard.stop_endless_tasks=Endlose Aufgaben stoppen dashboard.cancel_abandoned_jobs=Aufgegebene Jobs abbrechen +dashboard.start_schedule_tasks=Terminierte Aufgaben starten +dashboard.sync_branch.started=Synchronisierung der Branches gestartet +dashboard.rebuild_issue_indexer=Issue-Indexer neu bauen users.user_manage_panel=Benutzerkontenverwaltung users.new_account=Benutzerkonto erstellen @@ -2649,6 +2795,9 @@ users.full_name=Vollständiger Name users.activated=Aktiviert users.admin=Administrator users.restricted=Eingeschränkt +users.reserved=Reserviert +users.bot=Bot +users.remote=Remote users.2fa=2FA users.repos=Repos users.created=Registriert am @@ -2695,6 +2844,7 @@ users.list_status_filter.is_prohibit_login=Login verbieten users.list_status_filter.not_prohibit_login=Login erlaubt users.list_status_filter.is_2fa_enabled=2FA aktiviert users.list_status_filter.not_2fa_enabled=2FA deaktiviert +users.details=Benutzerdetails emails.email_manage_panel=Benutzer-E-Mail-Verwaltung emails.primary=Primär @@ -2726,10 +2876,12 @@ repos.stars=Favoriten repos.forks=Forks repos.issues=Issues repos.size=Größe +repos.lfs_size=LFS-Größe packages.package_manage_panel=Paketverwaltung packages.total_size=Gesamtgröße: %s packages.unreferenced_size=Nicht referenzierte Größe: %s +packages.cleanup=Veraltete Daten löschen packages.owner=Besitzer packages.creator=Ersteller packages.name=Name @@ -2740,12 +2892,12 @@ packages.size=Größe packages.published=Veröffentlicht defaulthooks=Standard-Webhooks -defaulthooks.desc=Webhooks senden automatisch eine HTTP-POST-Anfrage an einen Server, wenn bestimmte Gitea-Events ausgelöst werden. Hier definierte Webhooks sind die Standardwerte, die in alle neuen Repositories kopiert werden. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch). +defaulthooks.desc=Webhooks senden automatisch eine HTTP-POST-Anfrage an einen Server, wenn bestimmte Gitea-Events ausgelöst werden. Hier definierte Webhooks sind die Standardwerte, die in alle neuen Repositories kopiert werden. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch). defaulthooks.add_webhook=Standard-Webhook hinzufügen defaulthooks.update_webhook=Standard-Webhook aktualisieren systemhooks=System-Webhooks -systemhooks.desc=Webhooks senden automatisch HTTP-POST-Anfragen an einen Server, wenn bestimmte Gitea-Events ausgelöst werden. Hier definierte Webhooks werden auf alle Repositories des Systems übertragen, beachte daher mögliche Performance-Einbrüche. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch). +systemhooks.desc=Webhooks senden automatisch HTTP-POST-Anfragen an einen Server, wenn bestimmte Gitea-Events ausgelöst werden. Hier definierte Webhooks werden auf alle Repositories des Systems übertragen, beachte daher mögliche Performance-Einbrüche. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch). systemhooks.add_webhook=System-Webhook hinzufügen systemhooks.update_webhook=System-Webhook aktualisieren @@ -2838,6 +2990,7 @@ auths.sspi_default_language=Standardsprache für Benutzer auths.sspi_default_language_helper=Standardsprache für Benutzer, die automatisch mit der SSPI Authentifizierungsmethode erstellt wurden. Leer lassen, wenn du es bevorzugst, dass eine Sprache automatisch erkannt wird. auths.tips=Tipps auths.tips.oauth2.general=OAuth2-Authentifizierung +auths.tips.oauth2.general.tip=Beim Registrieren einer OAuth2-Anwendung sollte die Callback-URL folgendermaßen lauten: auths.tip.oauth2_provider=OAuth2-Anbieter auths.tip.bitbucket=Registriere einen neuen OAuth-Consumer unter https://bitbucket.org/account/user//oauth-consumers/new und füge die Berechtigung „Account“ – „Read“ hinzu. auths.tip.nextcloud=Registriere über das "Settings -> Security -> OAuth 2.0 client"-Menü einen neuen "OAuth consumer" auf der Nextcloud-Instanz @@ -2849,7 +3002,7 @@ auths.tip.google_plus=Du erhältst die OAuth2-Client-Zugangsdaten in der Google- auths.tip.openid_connect=Benutze die OpenID-Connect-Discovery-URL (/.well-known/openid-configuration), um die Endpunkte zu spezifizieren auths.tip.twitter=Gehe auf https://dev.twitter.com/apps, erstelle eine Anwendung und stelle sicher, dass die Option „Allow this application to be used to Sign in with Twitter“ aktiviert ist auths.tip.discord=Erstelle unter https://discordapp.com/developers/applications/me eine neue Anwendung. -auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://docs.gitea.io/en-us/oauth2-provider/ +auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://docs.gitea.com/development/oauth2-provider/ auths.tip.yandex=`Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem "Yandex.Passport API" Bereich: "Zugriff auf E-Mail-Adresse", "Zugriff auf Benutzeravatar" und "Zugriff auf Benutzername, Vor- und Nachname, Geschlecht"` auths.tip.mastodon=Gebe eine benutzerdefinierte URL für die Mastodon-Instanz ein, mit der du dich authentifizieren möchtest (oder benutze die standardmäßige) auths.edit=Authentifikationsquelle bearbeiten @@ -2879,6 +3032,7 @@ config.disable_router_log=Router-Log deaktivieren config.run_user=Ausführen als config.run_mode=Laufzeit-Modus config.git_version=Git-Version +config.app_data_path=App-Datenpfad config.repo_root_path=Repository-Wurzelpfad config.lfs_root_path=LFS-Wurzelpfad config.log_file_root_path=Logdateipfad @@ -3000,7 +3154,6 @@ config.access_log_mode=Access-Log-Modus config.access_log_template=Zugriffslog-Vorlage config.xorm_log_sql=SQL protokollieren -config.get_setting_failed=Holen der Einstellung %s fehlgeschlagen config.set_setting_failed=Konfiguration von %s ist fehlgeschlagen monitor.stats=Statistiken @@ -3030,10 +3183,10 @@ monitor.queue.name=Name monitor.queue.type=Typ monitor.queue.exemplar=Beispieltyp monitor.queue.numberworkers=Anzahl der Worker +monitor.queue.activeworkers=Aktive Worker monitor.queue.maxnumberworkers=Maximale Anzahl der Worker monitor.queue.numberinqueue=Nummer in der Warteschlange -monitor.queue.review=Konfiguration überprüfen -monitor.queue.review_add=Worker hinzufügen/prüfen +monitor.queue.review_add=Worker hinzufügen / prüfen monitor.queue.settings.title=Pool-Einstellungen monitor.queue.settings.desc=Pools wachsen dynamisch basierend auf der Blockierung der Arbeitswarteschlange. monitor.queue.settings.maxnumberworkers=Maximale Anzahl an Workern @@ -3154,6 +3307,7 @@ desc=Repository-Pakete verwalten. empty=Noch keine Pakete vorhanden. empty.documentation=Weitere Informationen zur Paket-Registry findest Du in der Dokumentation. empty.repo=Hast du ein Paket hochgeladen, das hier nicht angezeigt wird? Gehe zu den Paketeinstellungen und verlinke es mit diesem Repo. +registry.documentation=Für weitere Informationen zur %s-Registry, schaue in der Dokumentation nach. filter.type=Typ filter.type.all=Alle filter.no_result=Keine Ergebnisse mit diesen Kriterien gefunden. @@ -3243,6 +3397,8 @@ pub.install=Um das Paket mit Dart zu installieren, führe den folgenden Befehl a pypi.requires=Erfordert Python pypi.install=Nutze folgenden Befehl, um das Paket mit pip zu installieren: rpm.registry=Diese Registry über die Kommandozeile einrichten: +rpm.distros.redhat=auf RedHat-basierten Distributionen +rpm.distros.suse=auf SUSE-basierten Distributionen rpm.install=Nutze folgenden Befehl, um das Paket zu installieren: rubygems.install=Um das Paket mit gem zu installieren, führe den folgenden Befehl aus: rubygems.install2=oder füg es zum Gemfile hinzu: @@ -3267,14 +3423,17 @@ settings.delete.success=Das Paket wurde gelöscht. settings.delete.error=Löschen des Pakets fehlgeschlagen. owner.settings.cargo.title=Cargo-Registry-Index owner.settings.cargo.initialize=Index initialisieren +owner.settings.cargo.initialize.description=Ein spezielles Index-Repository wird benötigt, um die Cargo-Registry zu nutzen. Diese Option wird dieses Repository (neu) erstellen und automatisch konfigurieren. owner.settings.cargo.initialize.error=Cargo-Index konnte nicht initialisiert werden: %v owner.settings.cargo.initialize.success=Der Cargo-Index wurde erfolgreich erstellt. owner.settings.cargo.rebuild=Index neu erstellen +owner.settings.cargo.rebuild.description=Neubauen kann hilfreich sein, wenn der Index nicht mit den gespeicherten Cargo-Paketen synchronisiert ist. owner.settings.cargo.rebuild.error=Cargo-Index konnte nicht neu erstellt werden: %v owner.settings.cargo.rebuild.success=Der Cargo-Index wurde erfolgreich neu erstellt. owner.settings.cleanuprules.title=Bereinigungsregeln verwalten owner.settings.cleanuprules.add=Bereinigungsregel hinzufügen owner.settings.cleanuprules.edit=Bereinigungsregel bearbeiten +owner.settings.cleanuprules.none=Keine Bereinigungs-Regeln verfügbar. Bitte konsultiere die Dokumentation. owner.settings.cleanuprules.preview=Vorschau der Bereinigungsregel owner.settings.cleanuprules.preview.overview=%d Pakete sollen entfernt werden. owner.settings.cleanuprules.preview.none=Bereinigungsregel stimmt mit keinem Paket überein. @@ -3293,6 +3452,7 @@ owner.settings.cleanuprules.success.update=Bereinigungsregel wurde aktualisiert. owner.settings.cleanuprules.success.delete=Bereinigungsregel wurde gelöscht. owner.settings.chef.title=Chef-Registry owner.settings.chef.keypair=Schlüsselpaar generieren +owner.settings.chef.keypair.description=Ein Schlüsselpaar ist notwendig, um mit der Chef-Registry zu authentifizieren. Wenn du bereits eins erstellt hast, wird dieses durch eine Neuerstellung verworfen. [secrets] secrets=Secrets @@ -3319,6 +3479,7 @@ status.waiting=Wartend status.running=Laufend status.success=Erfolg status.failure=Fehler +status.cancelled=Abgebrochen status.skipped=Übersprungen status.blocked=Blockiert @@ -3335,6 +3496,7 @@ runners.labels=Labels runners.last_online=Letzte Online-Zeit runners.runner_title=Runner runners.task_list=Letzte Aufgaben dieses Runners +runners.task_list.no_tasks=Es gibt noch keine Aufgabe. runners.task_list.run=Ausführen runners.task_list.status=Status runners.task_list.repository=Repository @@ -3355,17 +3517,45 @@ runners.status.idle=Inaktiv runners.status.active=Aktiv runners.status.offline=Offline runners.version=Version +runners.reset_registration_token=Registrierungs-Token zurücksetzen runners.reset_registration_token_success=Runner-Registrierungstoken erfolgreich zurückgesetzt runs.all_workflows=Alle Workflows runs.commit=Commit +runs.scheduled=Geplant +runs.pushed_by=gepusht von runs.invalid_workflow_helper=Die Workflow-Konfigurationsdatei ist ungültig. Bitte überprüfe Deine Konfigurationsdatei: %s runs.no_matching_runner_helper=Kein passender Runner: %s +runs.actor=Initiator runs.status=Status +runs.actors_no_select=Alle Initiatoren +runs.status_no_select=Alle Status +runs.no_results=Keine passenden Ergebnisse gefunden. +runs.no_runs=Der Workflow hat noch keine Ausführungen. +workflow.disable=Workflow deaktivieren +workflow.disable_success=Workflow '%s' erfolgreich deaktiviert. +workflow.enable=Workflow aktivieren +workflow.enable_success=Workflow '%s' erfolgreich aktiviert. +workflow.disabled=Workflow ist deaktiviert. need_approval_desc=Um Workflows für den Pull-Request eines Forks auszuführen, ist eine Genehmigung erforderlich. +variables=Variablen +variables.management=Variablenverwaltung +variables.creation=Variable hinzufügen +variables.none=Es gibt noch keine Variablen. +variables.deletion=Variable entfernen +variables.deletion.description=Das Entfernen einer Variable ist dauerhaft und kann nicht rückgängig gemacht werden. Fortfahren? +variables.description=Variablen werden an bestimmte Aktionen übergeben und können nicht anderweitig gelesen werden. +variables.id_not_exist=Variable mit ID %d existiert nicht. +variables.edit=Variable bearbeiten +variables.deletion.failed=Fehler beim Entfernen der Variable. +variables.deletion.success=Die Variable wurde entfernt. +variables.creation.failed=Fehler beim Hinzufügen der Variable. +variables.creation.success=Die Variable „%s“ wurde hinzugefügt. +variables.update.failed=Fehler beim Bearbeiten der Variable. +variables.update.success=Die Variable wurde bearbeitet. [projects] type-1.display_name=Individuelles Projekt @@ -3373,5 +3563,10 @@ type-2.display_name=Repository-Projekt type-3.display_name=Organisationsprojekt [git.filemode] +changed_filemode=%[1]s → %[2]s +directory=Verzeichnis +normal_file=Normale Datei +executable_file=Ausführbare Datei symbolic_link=Softlink +submodule=Submodul diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index a07db7132b..2dc615a1b3 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -120,6 +120,8 @@ unpin=Άφεση artifacts=Αντικείμενα +archived=Αρχειοθετήθηκε + concept_system_global=Γενικό concept_user_individual=Ατομικό concept_code_repository=Αποθετήριο @@ -175,7 +177,6 @@ network_error=Σφάλμα δικτύου [startpage] app_desc=Μια ανώδυνη, αυτο-φιλοξενούμενη υπηρεσία Git install=Εύκολο στην εγκατάσταση -install_desc=Απλά εκτελέστε το δυαδικό για την πλατφόρμα σας, στείλτε το με Docker, ή πάρτε το πακέτο. platform=Πολυπλατφορμικό platform_desc=Ο Gitea τρέχει οπουδήποτε Go μπορεί να γίνει compile για: Windows, macOS, Linux, ARM, κλπ. Επιλέξτε αυτό που αγαπάτε! lightweight=Ελαφρύ @@ -291,6 +292,7 @@ invalid_password_algorithm=Μη έγκυρος αλγόριθμος κωδικο password_algorithm_helper=Ορίστε τον αλγόριθμο κατακερματισμού για το κωδικό πρόσβασης. Οι αλγόριθμοι διαφέρουν σε απαιτήσεις και αντοχή. Ο αλγόριθμος argon2 είναι αρκετά ασφαλής, αλλά χρησιμοποιεί πολλή μνήμη και μπορεί να είναι ακατάλληλος για μικρά συστήματα. enable_update_checker=Ενεργοποίηση Ελεγκτή Ενημερώσεων enable_update_checker_helper=Ελέγχει περιοδικά για νέες εκδόσεις κάνοντας σύνδεση στο gitea.io. +env_config_keys=Ρυθμίσεις Περιβάλλοντος [home] uname_holder=Όνομα Χρήστη ή Διεύθυνση Email @@ -582,7 +584,6 @@ overview=Επισκόπηση following=Ακολουθεί follow=Ακολουθήστε unfollow=Να μην ακολουθώ -heatmap.loading=Φόρτωση heatmap… user_bio=Βιογραφικό disabled_public_activity=Αυτός ο χρήστης έχει απενεργοποιήσει τη δημόσια προβολή της δραστηριότητας. email_visibility.limited=Η διεύθυνση email σας είναι ορατή σε όλους τους ταυτοποιημένους χρήστες @@ -655,7 +656,6 @@ choose_new_avatar=Επιλέξτε νέα εικόνα update_avatar=Ενημέρωση Εικόνας delete_current_avatar=Διαγραφή Τρέχουσας Εικόνας uploaded_avatar_not_a_image=Το αρχείο που ανεβάσατε δεν είναι εικόνα. -uploaded_avatar_is_too_big=Το αρχείο έχει υπερβεί το μέγιστο μέγεθος. update_avatar_success=Η εικόνα σας έχει ενημερωθεί. update_user_avatar_success=Το avatar του χρήστη ενημερώθηκε. @@ -957,6 +957,7 @@ delete_preexisting_success=Διαγράφηκαν τα μη υιοθετημέν blame_prior=Προβολή ευθύνης πριν από αυτή την αλλαγή author_search_tooltip=Εμφάνιση το πολύ 30 χρηστών + transfer.accept=Αποδοχή Μεταφοράς transfer.accept_desc=`Μεταφορά στο "%s"` transfer.reject=Απόρριψη Μεταφοράς @@ -1217,6 +1218,11 @@ commit.cherry-pick=Cherry-pick commit.cherry-pick-header=Ανθολόγηση: %s commit.cherry-pick-content=Επιλέξτε κλάδο για να κάνετε ανθολόγηση σε αυτό: +commitstatus.error=Σφάλμα +commitstatus.failure=Αποτυχία +commitstatus.pending=Εκκρεμεί +commitstatus.success=Επιτυχές + ext_issues=Πρόσβαση στα Εξωτερικά Ζητήματα ext_issues.desc=Σύνδεση σε εξωτερικό εφαρμογή ζητημάτων. @@ -1408,9 +1414,9 @@ issues.ref_reopening_from=`αναφέρθηκε σε ένα pull issues.ref_closed_from=`έκλεισε αυτό το ζήτημα %[4]s %[2]s` issues.ref_reopened_from=`άνοιξε ξανά αυτό το ζήτημα %[4]s %[2]s` issues.ref_from=`από %[1]s` -issues.poster=Συντάκτης -issues.collaborator=Συνεργάτης -issues.owner=Ιδιοκτήτης +issues.author=Συγγραφέας +issues.role.owner=Ιδιοκτήτης +issues.role.member=Μέλος issues.re_request_review=Επαναίτηση ανασκόπησης issues.is_stale=Έχουν υπάρξει αλλαγές σε αυτό το PR από αυτή την αναθεώρηση issues.remove_request_review=Αφαίρεση αιτήματος αναθεώρησης @@ -1725,8 +1731,6 @@ milestones.edit_success=Το ορόσημο "%s" ενημερώθηκε. milestones.deletion=Διαγραφή Ορόσημου milestones.deletion_desc=Η διαγραφή ενός ορόσημου το αφαιρεί από όλα τα συναφή ζητήματα. Συνέχεια; milestones.deletion_success=Το ορόσημο έχει διαγραφεί. -milestones.filter_sort.closest_due_date=Πλησιέστερη παράδοση -milestones.filter_sort.furthest_due_date=Απώτερη παράδοση milestones.filter_sort.least_complete=Λιγότερο πλήρη milestones.filter_sort.most_complete=Περισσότερο πλήρη milestones.filter_sort.most_issues=Περισσότερα ζητήματα @@ -1873,7 +1877,6 @@ settings.mirror_settings.push_mirror.remote_url=URL Απομακρυσμένου settings.mirror_settings.push_mirror.add=Προσθήκη Είδωλου Push settings.sync_mirror=Συγχρονισμός Τώρα -settings.mirror_sync_in_progress=Ο συγχρονισμός ειδώλου είναι σε εξέλιξη. Ελέγξτε ξανά σε λίγο. settings.site=Ιστοσελίδα settings.update_settings=Ενημέρωση Ρυθμίσεων settings.update_mirror_settings=Ενημέρωση Ρυθμίσεων Ειδώλου @@ -2207,7 +2210,6 @@ settings.tags.protection.allowed.teams=Επιτρεπόμενες ομάδες settings.tags.protection.allowed.noone=Καμία settings.tags.protection.create=Προστασία Ετικέτας settings.tags.protection.none=Δεν υπάρχουν προστατευμένες ετικέτες. -settings.tags.protection.pattern.description=Μπορείτε να χρησιμοποιήσετε ένα μόνο όνομα ή ένα μοτίβο glob ή μια κανονική έκφραση για να ταιριάξετε πολλαπλές ετικέτες. Διαβάστε περισσότερα στον οδηγό προστατευμένων ετικετών. settings.bot_token=Διακριτικό Bot settings.chat_id=ID Συνομιλίας settings.matrix.homeserver_url=Homeserver URL @@ -2357,7 +2359,6 @@ release.releases_for=Κυκλοφορίες για %s release.tags_for=Ετικέτες για %s branch.name=Όνομα Κλάδου -branch.search=Αναζήτηση κλάδων branch.already_exists=Ήδη υπάρχει ένας κλάδος με το όνομα "%s". branch.delete_head=Διαγραφή branch.delete=`Διαγραφή του Κλάδου "%s"` @@ -2578,9 +2579,7 @@ dashboard.deleted_branches_cleanup=Εκκαθάριση διαγραμμένων dashboard.update_migration_poster_id=Ενημέρωση των ID συντακτών στη μεταγκατάσταση dashboard.git_gc_repos=Garbage collect όλων των αποθετηρίων dashboard.resync_all_sshkeys=Ενημέρωση του αρχείου '.ssh/authorized_keys' με τα κλειδιά SSH του Gitea. -dashboard.resync_all_sshkeys.desc=(Δεν απαιτείται για τον ενσωματωμένο διακομιστή SSH.) dashboard.resync_all_sshprincipals=Ενημέρωση του αρχείου '.ssh/authorized_principals' με τις αρχές SSH του Gitea. -dashboard.resync_all_sshprincipals.desc=(Δεν απαιτείται για τον ενσωματωμένο διακομιστή SSH.) dashboard.resync_all_hooks=Επανασυγχρονισμός των hook pre-receive, update και post-receive όλων των αποθετηρίων. dashboard.reinit_missing_repos=Επανεκκινήστε όλα τα αποθετήρια Git που λείπουν και για τα οποία υπάρχουν εγγραφές dashboard.sync_external_users=Συγχρονισμός δεδομένων εξωτερικών χρηστών @@ -2722,12 +2721,10 @@ packages.size=Μέγεθος packages.published=Δημοσιευμένα defaulthooks=Προεπιλεγμένα Webhooks -defaulthooks.desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν συμβαίνουν ορισμένα γεγονότα στο Gitea. Τα Webhooks που ορίζονται εδώ είναι προεπιλογή και θα αντιγραφούν σε όλα τα νέα αποθετήρια. Διαβάστε περισσότερα στον οδηγό webhooks. defaulthooks.add_webhook=Προσθήκη Προεπιλεγμένου Webhook defaulthooks.update_webhook=Ενημέρωση Προεπιλεγμένου Webhook systemhooks=Webhooks Συστήματος -systemhooks.desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν συμβαίνουν ορισμένα γεγονότα στο Gitea. Τα Webhooks που ορίζονται εδώ θα ενεργούν σε όλα τα αποθετήρια του συστήματος, γι 'αυτό παρακαλώ εξετάστε τυχόν επιπτώσεις απόδοσης που μπορεί να υπάρξουν. Διαβάστε περισσότερα στον οδηγό webhooks. systemhooks.add_webhook=Προσθήκη Webhook Συστήματος systemhooks.update_webhook=Ενημέρωση Webhook Συστήματος @@ -2831,7 +2828,6 @@ auths.tip.google_plus=Αποκτήστε τα διαπιστευτήρια πε auths.tip.openid_connect=Χρησιμοποιήστε το OpenID Connect Discovery URL (/.well known/openid-configuration) για να καθορίσετε τα τελικά σημεία auths.tip.twitter=Πηγαίνετε στο https://dev.twitter.com/apps, δημιουργήστε μια εφαρμογή και βεβαιωθείτε ότι η επιλογή “Allow this application to be used to Sign in with Twitter” είναι ενεργοποιημένη auths.tip.discord=Καταχωρήστε μια νέα εφαρμογή στο https://discordapp.com/developers/applications/me -auths.tip.gitea=Καταχωρήστε μια νέα εφαρμογή OAuth2. Ο οδηγός μπορεί να βρεθεί στο https://docs.gitea.io/en-us/oauth2-provider/ auths.tip.yandex=`Δημιουργήστε μια νέα εφαρμογή στο https://oauth.yandex.com/client/new. Επιλέξτε τα ακόλουθα δικαιώματα από την ενότητα "Yandex.Passport API": "Access to email address", "Access to user avatar" και "Access to username, first name and surname, gender"` auths.tip.mastodon=Εισαγάγετε ένα προσαρμομένο URL για την υπηρεσία mastodon με την οποία θέλετε να πιστοποιήσετε (ή να χρησιμοποιήσετε την προεπιλεγμένη) auths.edit=Επεξεργασία Πηγής Ταυτοποίησης @@ -2982,7 +2978,6 @@ config.access_log_mode=Λειτουργία Καταγραφών Πρόσβασ config.access_log_template=Πρότυπο Καταγραφής Προσβάσεων config.xorm_log_sql=Καταγραφή SQL -config.get_setting_failed=Αποτυχία λήψης ρύθμισης %s config.set_setting_failed=Αποτυχία ορισμού της ρύθμισης %s monitor.stats=Στατιστικά @@ -3014,8 +3009,6 @@ monitor.queue.exemplar=Τύπος Υποδείγματος monitor.queue.numberworkers=Αριθμός Εργατών monitor.queue.maxnumberworkers=Μέγιστος Αριθμός Εργατών monitor.queue.numberinqueue=Πλήθος Ουράς -monitor.queue.review=Εξέταση Ρυθμίσεων -monitor.queue.review_add=Εξέταση/Προσθήκη Εργατών monitor.queue.settings.title=Ρυθμίσεις Δεξαμενής monitor.queue.settings.desc=Οι δεξαμενές αυξάνονται δυναμικά όταν υπάρχει φραγή της ουράς των εργατών τους. monitor.queue.settings.maxnumberworkers=Μέγιστος Αριθμός Εργατών diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 39da4be179..84c457e9e8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -17,6 +17,7 @@ template = Template language = Language notifications = Notifications active_stopwatch = Active Time Tracker +tracked_time_summary = Summary of tracked time based on filters of issue list create_new = Create… user_profile_and_more = Profile and Settings… signed_in_as = Signed in as @@ -97,6 +98,7 @@ locked = Locked copy = Copy copy_url = Copy URL +copy_hash = Copy hash copy_content = Copy content copy_branch = Copy branch name copy_success = Copied! @@ -112,6 +114,7 @@ step2 = Step 2: error = Error error404 = The page you are trying to reach either does not exist or you are not authorized to view it. +go_back = Go Back never = Never unknown = Unknown @@ -123,6 +126,8 @@ unpin = Unpin artifacts = Artifacts +archived = Archived + concept_system_global = Global concept_user_individual = Individual concept_code_repository = Repository @@ -317,7 +322,6 @@ filter_by_team_repositories = Filter by team repositories feed_of = Feed of "%s" show_archived = Archived -archived = Archived show_both_archived_unarchived = Showing both archived and unarchived show_only_archived = Showing only archived show_only_unarchived = Showing only unarchived @@ -360,6 +364,7 @@ disable_register_prompt = Registration is disabled. Please contact your site adm disable_register_mail = Email confirmation for registration is disabled. manual_activation_only = Contact your site administrator to complete activation. remember_me = Remember This Device +remember_me.compromised = The login token is not valid anymore which may indicate a compromised account. Please check your account for unusual activities. forgot_password_title= Forgot Password forgot_password = Forgot password? sign_up_now = Need an account? Register now. @@ -942,6 +947,8 @@ fork_from = Fork From already_forked = You've already forked %s fork_to_different_account = Fork to a different account fork_visibility_helper = The visibility of a forked repository cannot be changed. +fork_branch = Branch to be cloned to the fork +all_branches = All branches fork_no_valid_owners = This repository can not be forked because there are no valid owners. use_template = Use this template clone_in_vsc = Clone in VS Code @@ -1007,8 +1014,14 @@ delete_preexisting = Delete pre-existing files delete_preexisting_content = Delete files in %s delete_preexisting_success = Deleted unadopted files in %s blame_prior = View blame prior to this change +blame.ignore_revs = Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view. +blame.ignore_revs.failed = Failed to ignore revisions in .git-blame-ignore-revs. author_search_tooltip = Shows a maximum of 30 users +tree_path_not_found_commit = Path %[1]s doesn't exist in commit %[2]s +tree_path_not_found_branch = Path %[1]s doesn't exist in branch %[2]s +tree_path_not_found_tag = Path %[1]s doesn't exist in tag %[2]s + transfer.accept = Accept Transfer transfer.accept_desc = Transfer to "%s" transfer.reject = Reject Transfer @@ -1018,10 +1031,8 @@ transfer.no_permission_to_reject = You do not have permission to reject this tra desc.private = Private desc.public = Public -desc.private_template = Private template -desc.public_template = Template +desc.template = Template desc.internal = Internal -desc.internal_template = Internal template desc.archived = Archived template.items = Template Items @@ -1273,6 +1284,7 @@ commits.signed_by_untrusted_user = Signed by untrusted user commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does not match committer commits.gpg_key_id = GPG Key ID commits.ssh_key_fingerprint = SSH Key Fingerprint +commits.view_path=View at this point in history commit.operations = Operations commit.revert = Revert @@ -1509,7 +1521,8 @@ issues.label_description = Description issues.label_color = Color issues.label_exclusive = Exclusive issues.label_archive = Archive Label -issues.label_archive_tooltip= Archived labels are excluded from the label search when applying labels to an issue. Existing labels on issues remain unaffected, allowing you to retire obsolete labels without losing information. +issues.label_archived_filter = Show archived labels +issues.label_archive_tooltip = Archived labels are excluded by default from the suggestions when searching by label. issues.label_exclusive_desc = Name the label scope/item to make it mutually exclusive with other scope/ labels. issues.label_exclusive_warning = Any conflicting scoped labels will be removed when editing the labels of an issue or pull request. issues.label_count = %d labels @@ -1996,7 +2009,8 @@ settings.mirror_settings.push_mirror.add = Add Push Mirror settings.mirror_settings.push_mirror.edit_sync_time = Edit mirror sync interval settings.sync_mirror = Synchronize Now -settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check back in a minute. +settings.pull_mirror_sync_in_progress = Pulling changes from the remote %s at the moment. +settings.push_mirror_sync_in_progress = Pushing changes to the remote %s at the moment. settings.site = Website settings.update_settings = Update Settings settings.update_mirror_settings = Update Mirror Settings @@ -2130,12 +2144,14 @@ settings.webhook_deletion_desc = Removing a webhook deletes its settings and del settings.webhook_deletion_success = The webhook has been removed. settings.webhook.test_delivery = Test Delivery settings.webhook.test_delivery_desc = Test this webhook with a fake event. +settings.webhook.test_delivery_desc_disabled = To test this webhook with a fake event, activate it. settings.webhook.request = Request settings.webhook.response = Response settings.webhook.headers = Headers settings.webhook.payload = Content settings.webhook.body = Body settings.webhook.replay.description = Replay this webhook. +settings.webhook.replay.description_disabled = To replay this webhook, activate it. settings.webhook.delivery.success = An event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history. settings.githooks_desc = "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations." settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook. @@ -2295,6 +2311,7 @@ settings.dismiss_stale_approvals_desc = When new commits that change the content settings.require_signed_commits = Require Signed Commits settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable. settings.protect_branch_name_pattern = Protected Branch Name Pattern +settings.protect_branch_name_pattern_desc = "Protected branch name patterns. See the documentation for pattern syntax. Examples: main, release/**" settings.protect_patterns = Patterns settings.protect_protected_file_patterns = "Protected file patterns (separated using semicolon ';'):" settings.protect_protected_file_patterns_desc = "Protected files are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. Multiple patterns can be separated using semicolon (';'). See github.com/gobwas/glob documentation for pattern syntax. Examples: .drone.yml, /docs/**/*.txt." @@ -2513,6 +2530,7 @@ branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be branch.restore = Restore Branch "%s" branch.download = Download Branch "%s" branch.rename = Rename Branch "%s" +branch.search = Search Branch branch.included_desc = This branch is part of the default branch branch.included = Included branch.create_new_branch = Create branch from branch: @@ -2723,9 +2741,7 @@ dashboard.deleted_branches_cleanup = Clean-up deleted branches dashboard.update_migration_poster_id = Update migration poster IDs dashboard.git_gc_repos = Garbage collect all repositories dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. -dashboard.resync_all_sshkeys.desc = (Not needed for the built-in SSH server.) dashboard.resync_all_sshprincipals = Update the '.ssh/authorized_principals' file with Gitea SSH principals. -dashboard.resync_all_sshprincipals.desc = (Not needed for the built-in SSH server.) dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist dashboard.sync_external_users = Synchronize external user data @@ -2780,6 +2796,9 @@ users.full_name = Full Name users.activated = Activated users.admin = Admin users.restricted = Restricted +users.reserved = Reserved +users.bot = Bot +users.remote = Remote users.2fa = 2FA users.repos = Repos users.created = Created @@ -3136,7 +3155,6 @@ config.access_log_mode = Access Log Mode config.access_log_template = Access Log Template config.xorm_log_sql = Log SQL -config.get_setting_failed = Get setting %s failed config.set_setting_failed = Set setting %s failed monitor.stats = Stats @@ -3500,6 +3518,7 @@ runners.status.idle = Idle runners.status.active = Active runners.status.offline = Offline runners.version = Version +runners.reset_registration_token = Reset registration token runners.reset_registration_token_success = Runner registration token reset successfully runs.all_workflows = All Workflows diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index ff6175cf01..5654d30cec 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -4,6 +4,7 @@ explore=Explorar help=Ayuda logo=Logotipo sign_in=Iniciar sesión +sign_in_with_provider=Iniciar sesión con %s sign_in_or=o sign_out=Cerrar sesión sign_up=Registrarse @@ -19,6 +20,7 @@ active_stopwatch=Rastreador de tiempo activo create_new=Crear… user_profile_and_more=Perfil y ajustes… signed_in_as=Identificado como +enable_javascript=Este sitio web requiere JavaScript. toc=Tabla de contenidos licenses=Licencias return_to_gitea=Volver a Gitea @@ -56,6 +58,7 @@ new_mirror=Nueva réplica new_fork=Nuevo fork de repositorio new_org=Nueva organización new_project=Nuevo Proyecto +new_project_column=Columna nueva manage_org=Administrar organizaciones admin_panel=Administración del sitio account_settings=Configuraciones de la cuenta @@ -77,21 +80,29 @@ milestones=Hitos ok=OK cancel=Cancelar +retry=Reintentar +rerun=Re-ejecutar +rerun_all=Volver a ejecutar todos los trabajos save=Guardar add=Añadir add_all=Añadir todo remove=Eliminar remove_all=Eliminar todos +remove_label_str=`Eliminar elemento "%s"` edit=Editar enabled=Activo disabled=Desactivado +locked=Bloqueado copy=Copiar copy_url=Copiar URL +copy_hash=Copiar hash +copy_content=Copiar contenido copy_branch=Copiar nombre de rama copy_success=¡Copiado! copy_error=Copiar falló +copy_type_unsupported=No se puede copiar este tipo de archivo write=Escribir preview=Vista previa @@ -102,26 +113,62 @@ step2=Paso 2: error=Error error404=La página a la que está intentando acceder o no existe o no está autorizado para verla. +go_back=Volver never=Nunca +unknown=Desconocido rss_feed=Fuentes RSS +pin=Anclar +unpin=Desanclar +artifacts=Artefactos +archived=Archivado + +concept_system_global=Global +concept_user_individual=Individual concept_code_repository=Repositorio concept_user_organization=Organización +show_timestamps=Mostrar marcas de tiempo +show_log_seconds=Mostrar segundos +show_full_screen=Mostrar pantalla completa +download_logs=Descargar registros +confirm_delete_selected=¿Borrar todos los elementos seleccionados? name=Nombre value=Valor [aria] +navbar=Barra de navegación +footer=Pie +footer.software=Acerca del Software +footer.links=Enlaces [heatmap] +number_of_contributions_in_the_last_12_months=%s contribuciones en los últimos 12 meses +no_contributions=No hay contribuciones +less=Menos +more=Más [editor] +buttons.heading.tooltip=Añadir encabezado +buttons.bold.tooltip=Añadir texto en negrita +buttons.italic.tooltip=Añadir texto en cursiva +buttons.quote.tooltip=Citar texto +buttons.code.tooltip=Añadir código +buttons.link.tooltip=Añadir un enlace +buttons.list.unordered.tooltip=Añadir una lista +buttons.list.ordered.tooltip=Añadir una lista numerada +buttons.list.task.tooltip=Añadir una lista de tareas +buttons.mention.tooltip=Mencionar un usuario o equipo +buttons.ref.tooltip=Referir a una incidencia o pull request +buttons.switch_to_legacy.tooltip=Utilizar el editor antiguo en su lugar +buttons.enable_monospace_font=Activar fuente monoespaciada +buttons.disable_monospace_font=Desactivar fuente monoespaciada [filter] string.asc=A - Z @@ -129,6 +176,7 @@ string.desc=Z - A [error] occurred=Ha ocurrido un error +report_message=Si crees que este es un error de Gitea, por favor busca incidencias en GitHub o abre una nueva incidencia si es necesario. missing_csrf=Solicitud incorrecta: sin token CSRF invalid_csrf=Solicitud incorrecta: el token CSRF no es válido not_found=El objetivo no pudo ser encontrado. @@ -137,7 +185,7 @@ network_error=Error de red [startpage] app_desc=Un servicio de Git autoalojado y sin complicaciones install=Fácil de instalar -install_desc=Simplemente arranca el binario para su plataforma. O utilice Gitea con Docker, o utilice el paquete. +install_desc=Simplemente ejecuta el binario para tu plataforma, lánzalo con Dockero consíguelo empaquetado. platform=Multiplataforma platform_desc=Gitea funciona en cualquier platforma Go puede compilarlo en: Windows, macOS, Linux, ARM, etc. ¡Elige tu favorita! lightweight=Ligero @@ -182,6 +230,7 @@ repo_path_helper=Los repositorios Git se guardarán en este directorio. lfs_path=Ruta raíz de Git LFS lfs_path_helper=Los archivos almacenados con Git LFS se almacenarán en este directorio. Déjelo vacío para deshabilitarlo. run_user=Ejecutar como usuario +run_user_helper=El nombre de usuario del sistema operativo que ejecuta Gitea. Tenga en cuenta que este usuario debe tener acceso a la ruta raíz del repositorio. domain=Dominio del Servidor domain_helper=Dominio o dirección de host para el servidor. ssh_port=Puerto de servidor SSH @@ -220,6 +269,7 @@ openid_signup_popup=Habilitar autorregistro de usuario basado en OpenID. enable_captcha=Requerir CAPTCHA durante el registro enable_captcha_popup=Requerir CAPTCHA para auto-registro de usuario. require_sign_in_view=Requerir inicio de sesión para ver páginas +require_sign_in_view_popup=Limitar el acceso a los usuarios conectados. Los visitantes sólo verán las páginas de inicio de sesión y de registro. admin_setting_desc=Crear una cuenta de administrador es opcional. El primer usuario registrado se convertirá automáticamente en administrador. admin_title=Configuración de la cuenta de administrador admin_name=Nombre de usuario del administrador @@ -230,6 +280,7 @@ install_btn_confirm=Instalar Gitea test_git_failed=Fallo al probar el comando 'git': %v sqlite3_not_available=Esta versión de Gitea no soporta SQLite3. Por favor, descarga la versión binaria oficial de %s (no la versión 'gobuild'). invalid_db_setting=La configuración de la base de datos no es válida: %v +invalid_db_table=La tabla "%s" de la base de datos no es válida: %v invalid_repo_path=La ruta de la raíz del repositorio no es válida: %v invalid_app_data_path=La ruta de datos de la aplicación (APP_DATA_PATH) no es válida: %v run_user_not_match=El nombre de usuario 'ejecutar como' no es el nombre actual de usuario: %s -> %s @@ -247,6 +298,12 @@ default_enable_timetracking_popup=Activar el seguimiento de tiempo para nuevos r no_reply_address=Dominio de correos electrónicos ocultos no_reply_address_helper=Nombre de dominio para usuarios con dirección de correo electrónico oculta. Por ejemplo, el usuario 'joe' quedará registrado en Git como 'joe@noreply.example.org' si el dominio de correo electrónico oculto se establece a 'noreply.example.org'. password_algorithm=Algoritmo Hash de Contraseña +invalid_password_algorithm=Algoritmo hash de contraseña no válido +password_algorithm_helper=Establece el algoritmo de hashing de contraseña. Los algoritmos tienen diferentes requisitos y fuerza. El algoritmo argon2 es bastante seguro, pero usa mucha memoria y puede ser inapropiado para sistemas pequeños. +enable_update_checker=Activar comprobador de actualizaciones +enable_update_checker_helper=Comprueba el lanzamiento de nuevas versiones periódicamente en gitea.io. +env_config_keys=Configuración del entorno +env_config_keys_prompt=Las siguientes variables de entorno también se aplicarán a su archivo de configuración: [home] uname_holder=Nombre de usuario o correo electrónico @@ -280,6 +337,7 @@ repos=Repositorios users=Usuarios organizations=Organizaciones search=Buscar +go_to=Ir a code=Código search.type.tooltip=Tipo de búsqueda search.fuzzy=Parcial @@ -291,6 +349,7 @@ repo_no_results=No se ha encontrado ningún repositorio coincidente. user_no_results=No se ha encontrado ningún usuario coincidente. org_no_results=No se ha encontrado ninguna organización coincidente. code_no_results=No se ha encontrado código de fuente que coincida con su término de búsqueda. +code_search_results=Resultados de búsqueda para «%s» code_last_indexed_at=Indexado por última vez %s relevant_repositories_tooltip=Repositorios que son bifurcaciones o que no tienen ningún tema, ningún icono, y ninguna descripción están ocultos. relevant_repositories=Solo se muestran repositorios relevantes, mostrar resultados sin filtrar. @@ -307,6 +366,7 @@ remember_me=Recordar este Dispositivo forgot_password_title=He olvidado mi contraseña forgot_password=¿Has olvidado tu contraseña? sign_up_now=¿Necesitas una cuenta? Regístrate ahora. +sign_up_successful=La cuenta se ha creado correctamente. ¡Bienvenido! confirmation_mail_sent_prompt=Un nuevo correo de confirmación se ha enviado a %s. Comprueba tu bandeja de entrada en las siguientes %s para completar el registro. must_change_password=Actualizar su contraseña allow_password_change=Obligar al usuario a cambiar la contraseña (recomendado) @@ -314,6 +374,7 @@ reset_password_mail_sent_prompt=Un correo de confirmación se ha enviado a %s active_your_account=Activa tu cuenta account_activated=La cuenta ha sido activada prohibit_login=Ingreso prohibido +prohibit_login_desc=Su cuenta no puede iniciar sesión, póngase en contacto con el administrador de su sitio. resent_limit_prompt=Ya ha solicitado recientemente un correo de activación. Por favor, espere 3 minutos y vuelva a intentarlo. has_unconfirmed_mail=Hola %s, su correo electrónico (%s) no está confirmado. Si no ha recibido un correo de confirmación o necesita que lo enviemos de nuevo, por favor, haga click en el siguiente botón. resend_mail=Haga click aquí para reenviar su correo electrónico de activación @@ -321,7 +382,10 @@ email_not_associate=Esta dirección de correo electrónico no esta asociada a ni send_reset_mail=Enviar correo de recuperación de cuenta reset_password=Recuperación de cuenta invalid_code=Su código de confirmación no es válido o ha caducado. +invalid_code_forgot_password=Su código de confirmación no es válido o ha caducado. Haga clic aquí para iniciar una nueva sesión. +invalid_password=Su contraseña no coincide con la contraseña utilizada para crear la cuenta. reset_password_helper=Recuperar cuenta +reset_password_wrong_user=Has iniciado sesión como %s, pero el enlace de recuperación de la cuenta está destinado a %s password_too_short=La longitud de la contraseña no puede ser menor a %d caracteres. non_local_account=Los usuarios no locales no pueden actualizar su contraseña a través de la interfaz web de Gitea. verify=Verificar @@ -346,6 +410,7 @@ openid_connect_title=Accede con una cuenta existente openid_connect_desc=La URI OpenID elegida es desconocida. Asóciela a una nueva cuenta aquí. openid_register_title=Crear una nueva cuenta openid_register_desc=La URI OpenID elegida es desconocida. Asóciela a una nueva cuenta aquí. +openid_signin_desc=Introduzca su URI de OpenID. Por ejemplo: alice.openid.example.org o https://openid.example.org/alice. disable_forgot_password_mail=La recuperación de cuentas está desactivada porque no hay correo electrónico configurado. Por favor, contacte con el administrador del sitio. disable_forgot_password_mail_admin=La recuperación de cuentas solo está disponible cuando se configura el correo electrónico configurado. Por favor, configure el correo electrónico para permitir la recuperación de cuentas. email_domain_blacklisted=No puede registrarse con su correo electrónico. @@ -355,11 +420,14 @@ authorize_application_created_by=Esta aplicación fue creada por %s. authorize_application_description=Si concede el acceso, podrá acceder y escribir a toda la información de su cuenta, incluyendo repositorios privado y organizaciones. authorize_title=¿Autorizar a "%s" a acceder a su cuenta? authorization_failed=Autorización fallida +authorization_failed_desc=La autorización ha fallado porque hemos detectado una solicitud no válida. Por favor, póngase en contacto con el responsable de la aplicación que ha intentado autorizar. sspi_auth_failed=Fallo en la autenticación SSPI +password_pwned=La contraseña que eligió está en una lista de contraseñas robadas previamente expuestas en violaciones de datos públicos. Por favor, inténtelo de nuevo con una contraseña diferente y considere cambiar esta contraseña también en otros lugares. password_pwned_err=No se pudo completar la solicitud a HaveIBeenPwned [mail] view_it_on=Ver en %s +reply=o responde directamente a este correo electrónico link_not_working_do_paste=¿No funciona? Intenta copiarlo y pegarlo en tu navegador. hi_user_x=Hola %s, @@ -369,6 +437,7 @@ activate_account.text_1=¡Hola %[1]s, gracias por registrarse en %[2]s! activate_account.text_2=Por favor, haga clic en el siguiente enlace para activar su cuenta dentro de %s: activate_email=Verifique su correo electrónico +activate_email.title=%s, por favor verifique su dirección de correo electrónico activate_email.text=Por favor, haga clic en el siguiente enlace para verificar su dirección de correo electrónico dentro de %s: register_notify=¡Bienvenido a Gitea @@ -425,6 +494,7 @@ team_invite.text_3=Nota: Esta invitación estaba destinada a %[1]s. Si no espera [modal] yes=Sí no=No +confirm=Confirmar cancel=Cancelar modify=Actualizar @@ -459,8 +529,12 @@ size_error=` debe ser de tamaño %s.` min_size_error=` debe contener al menos %s caracteres.` max_size_error=` debe contener como máximo %s caracteres.` email_error=` no es una dirección de correo válida.` +url_error=`"%s" no es una URL válida.` +include_error=` debe contener la subcadena "%s".` glob_pattern_error=` el patrón globo no es válido: %s.` regex_pattern_error=` el patrón de regex no es válido: %s.` +username_error=` sólo puede contener caracteres alfanuméricos ('0-9','a-z','A-Z'), guión ('-'), guión bajo ('_') y punto ('.'). No puede comenzar o terminar con caracteres no alfanuméricos, y los caracteres no alfanuméricos consecutivos también están prohibidos.` +invalid_group_team_map_error=` la asignación no es válida: %s` unknown_error=Error desconocido: captcha_incorrect=El código CAPTCHA no es correcto. password_not_match=Las contraseñas no coinciden. @@ -468,6 +542,7 @@ lang_select_error=Seleccione un idioma de la lista. username_been_taken=El nombre de usuario ya está en uso. username_change_not_local_user=Los usuarios que no son locales no tienen permitido cambiar su nombre de usuario. +username_has_not_been_changed=El nombre de usuario no ha sido cambiado repo_name_been_taken=El nombre del repositorio ya está usado. repository_force_private=Forzar Privado está habilitado: los repositorios privados no pueden hacerse públicos. repository_files_already_exist=Ya existen archivos para este repositorio. Póngase en contacto con el administrador del sistema. @@ -481,6 +556,7 @@ team_name_been_taken=Ya existe un equipo con este nombre. team_no_units_error=Permitir el acceso a por lo menos una sección del repositorio. email_been_used=La dirección de correo electrónico ya está usada. email_invalid=La dirección de correo electrónico no es válida. +openid_been_used=La dirección de OpenID "%s" ya está en uso. username_password_incorrect=El nombre de usuario o la contraseña son incorrectos. password_complexity=La contraseña no cumple los requisitos de complejidad: password_lowercase_one=Al menos una letra minúscula @@ -496,17 +572,26 @@ team_not_exist=Este equipo no existe. last_org_owner=No puedes eliminar al último usuario del equipo de 'propietarios'. Todas las organizaciones deben tener al menos un propietario. cannot_add_org_to_team=Una organización no puede ser añadida como miembro de un equipo. duplicate_invite_to_team=El usuario ya fue invitado como miembro del equipo. +organization_leave_success=Ha abandonado correctamente la organización %s. invalid_ssh_key=No se puede verificar su clave SSH: %s invalid_gpg_key=No se puede verificar su clave GPG: %s invalid_ssh_principal=Principal no válido: %s +must_use_public_key=La clave que proporcionó es una clave privada. No cargue su clave privada en ningún sitio. Utilice su clave pública en su lugar. +unable_verify_ssh_key=No se puede verificar la clave SSH, comprueba si hay errores. auth_failed=Autenticación fallo: %v +still_own_repo=Su cuenta posee uno o más repositorios, elimínalos o transfiérelos primero. +still_has_org=Tu cuenta es miembro de una o más organizaciones, déjalas primero. +still_own_packages=Su cuenta posee uno o más paquetes, elimínalos primero. +org_still_own_repo=Esta organización todavía posee uno o más repositorios, elimínalos o transfiérelos primero. +org_still_own_packages=Esta organización todavía posee uno o más paquetes, elimínalos primero. target_branch_not_exist=La rama de destino no existe [user] change_avatar=Cambiar su avatar… +joined_on=Se unió el %s repositories=Repositorios activity=Actividad pública followers=Seguidores @@ -518,10 +603,16 @@ overview=Resumen following=Siguiendo follow=Seguir unfollow=Dejar de seguir -heatmap.loading=Cargando mapa de calor… user_bio=Biografía disabled_public_activity=Este usuario ha desactivado la visibilidad pública de la actividad. +email_visibility.limited=Tu dirección de correo electrónico es visible para todos los usuarios autenticados +email_visibility.private=Tu dirección de correo electrónico solo es visible para ti y los administradores +show_on_map=Mostrar este lugar en un mapa +settings=Ajustes del usuario +form.name_reserved=El nombre de usuario "%s" está reservado. +form.name_pattern_not_allowed=El patrón "%s" no está permitido en un nombre de usuario. +form.name_chars_not_allowed=El nombre de usuario "%s" contiene caracteres no válidos. [settings] profile=Perfil @@ -539,9 +630,13 @@ delete=Eliminar cuenta twofa=Autenticación de doble factor account_link=Cuentas vinculadas organization=Organizaciones +uid=UID webauthn=Llaves de Seguridad public_profile=Perfil público +biography_placeholder=¡Cuéntanos un poco sobre ti mismo! (Puedes usar Markdown) +location_placeholder=Comparte tu ubicación aproximada con otros +profile_desc=Controla cómo se muestra su perfil a otros usuarios. Tu dirección de correo electrónico principal se utilizará para notificaciones, recuperación de contraseña y operaciones de Git basadas en la web. password_username_disabled=Usuarios no locales no tienen permitido cambiar su nombre de usuario. Por favor, contacta con el administrador del sistema para más detalles. full_name=Nombre completo website=Página web @@ -549,14 +644,20 @@ location=Localización update_theme=Actualizar tema update_profile=Actualizar perfil update_language=Actualizar idioma +update_language_not_found=Idioma "%s" no está disponible. update_language_success=El idioma ha sido actualizado. update_profile_success=Tu perfil ha sido actualizado. change_username=Su nombre de usuario ha sido cambiado. +change_username_prompt=Nota: Cambiar su nombre de usuario también cambia la URL de su cuenta. +change_username_redirect_prompt=El nombre de usuario antiguo redirigirá hasta que alguien lo reclame. continue=Continuar cancel=Cancelar language=Idioma ui=Tema hidden_comment_types=Tipos de comentarios ocultos +hidden_comment_types_description=Los tipos de comentarios marcados aquí no se mostrarán dentro de las páginas de incidencia. Marcar "Etiqueta" por ejemplo elimina todos los comentarios " añadidos/eliminados