diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 9e290fb6a51..1051b0f2a2e 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,6 +1,6 @@
{
"name": "Gitea DevContainer",
- "image": "mcr.microsoft.com/devcontainers/go:1.21-bullseye",
+ "image": "mcr.microsoft.com/devcontainers/go:1.22-bullseye",
"features": {
// installs nodejs into container
"ghcr.io/devcontainers/features/node:1": {
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index 18e8138601b..e9991c02bae 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -12,6 +12,7 @@ plugins:
- "@eslint-community/eslint-plugin-eslint-comments"
- "@stylistic/eslint-plugin-js"
- eslint-plugin-array-func
+ - eslint-plugin-github
- eslint-plugin-i
- eslint-plugin-jquery
- eslint-plugin-no-jquery
@@ -209,6 +210,29 @@ rules:
func-names: [0]
func-style: [0]
getter-return: [2]
+ github/a11y-aria-label-is-well-formatted: [0]
+ github/a11y-no-title-attribute: [0]
+ github/a11y-no-visually-hidden-interactive-element: [0]
+ github/a11y-role-supports-aria-props: [0]
+ github/a11y-svg-has-accessible-name: [0]
+ github/array-foreach: [0]
+ github/async-currenttarget: [2]
+ github/async-preventdefault: [2]
+ github/authenticity-token: [0]
+ github/get-attribute: [0]
+ github/js-class-name: [0]
+ github/no-blur: [0]
+ github/no-d-none: [0]
+ github/no-dataset: [2]
+ github/no-dynamic-script-tag: [2]
+ github/no-implicit-buggy-globals: [2]
+ github/no-inner-html: [0]
+ github/no-innerText: [2]
+ github/no-then: [2]
+ github/no-useless-passive: [2]
+ github/prefer-observers: [2]
+ github/require-passive-events: [2]
+ github/unescaped-html-literal: [0]
grouped-accessor-pairs: [2]
guard-for-in: [0]
id-blacklist: [0]
@@ -272,7 +296,7 @@ rules:
jquery/no-delegate: [2]
jquery/no-each: [0]
jquery/no-extend: [2]
- jquery/no-fade: [0]
+ jquery/no-fade: [2]
jquery/no-filter: [0]
jquery/no-find: [0]
jquery/no-global-eval: [2]
@@ -285,7 +309,7 @@ rules:
jquery/no-is-function: [2]
jquery/no-is: [0]
jquery/no-load: [2]
- jquery/no-map: [0]
+ jquery/no-map: [2]
jquery/no-merge: [2]
jquery/no-param: [2]
jquery/no-parent: [0]
@@ -427,7 +451,7 @@ rules:
no-jquery/no-load: [2]
no-jquery/no-map-collection: [0]
no-jquery/no-map-util: [2]
- no-jquery/no-map: [0]
+ no-jquery/no-map: [2]
no-jquery/no-merge: [2]
no-jquery/no-node-name: [2]
no-jquery/no-noop: [2]
@@ -558,7 +582,6 @@ rules:
prefer-rest-params: [2]
prefer-spread: [2]
prefer-template: [2]
- quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
radix: [2, as-needed]
regexp/confusing-quantifier: [2]
regexp/control-character-escape: [2]
@@ -723,6 +746,7 @@ rules:
unicorn/no-this-assignment: [2]
unicorn/no-typeof-undefined: [2]
unicorn/no-unnecessary-await: [2]
+ unicorn/no-unnecessary-polyfills: [2]
unicorn/no-unreadable-array-destructuring: [0]
unicorn/no-unreadable-iife: [2]
unicorn/no-unused-properties: [2]
@@ -810,7 +834,7 @@ rules:
wc/no-constructor-params: [2]
wc/no-constructor: [2]
wc/no-customized-built-in-elements: [2]
- wc/no-exports-with-element: [2]
+ wc/no-exports-with-element: [0]
wc/no-invalid-element-name: [2]
wc/no-invalid-extends: [2]
wc/no-method-prefixed-with-on: [2]
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 624a2d97db1..1447a6ea322 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1 @@
open_collective: gitea
-custom: https://www.bountysource.com/teams/gitea
diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644
index ebe95acf533..00000000000
--- a/.github/stale.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-# Configuration for probot-stale - https://github.com/probot/stale
-
-# Number of days of inactivity before an Issue or Pull Request becomes stale
-daysUntilStale: 60
-
-# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
-# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
-daysUntilClose: 14
-
-# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
-exemptLabels:
- - status/blocked
- - kind/security
- - lgtm/done
- - reviewed/confirmed
- - priority/critical
- - kind/proposal
-
-# Set to true to ignore issues in a project (defaults to false)
-exemptProjects: false
-
-# Set to true to ignore issues in a milestone (defaults to false)
-exemptMilestones: false
-
-# Label to use when marking as stale
-staleLabel: stale
-
-# Comment to post when marking as stale. Set to `false` to disable
-markComment: >
- This issue has been automatically marked as stale because it has not had recent activity.
- I am here to help clear issues left open even if solved or waiting for more insight.
- This issue will be closed if no further activity occurs during the next 2 weeks.
- If the issue is still valid just add a comment to keep it alive.
- Thank you for your contributions.
-
-# Comment to post when closing a stale Issue or Pull Request.
-closeComment: >
- This issue has been automatically closed because of inactivity.
- You can re-open it if needed.
-
-# Limit the number of actions per hour, from 1-30. Default is 30
-limitPerRun: 1
-
-# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
-pulls:
- daysUntilStale: 60
- daysUntilClose: 60
- markComment: >
- This pull request has been automatically marked as stale because it has not had
- recent activity. It will be closed if no further activity occurs during the next 2 months. Thank you
- for your contributions.
- closeComment: >
- This pull request has been automatically closed because of inactivity.
- You can re-open it if needed.
diff --git a/.github/workflows/cron-licenses.yml b/.github/workflows/cron-licenses.yml
index 6ef5813a64f..cd8386ecc52 100644
--- a/.github/workflows/cron-licenses.yml
+++ b/.github/workflows/cron-licenses.yml
@@ -11,7 +11,7 @@ jobs:
if: github.repository == 'go-gitea/gitea'
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
diff --git a/.github/workflows/cron-lock.yml b/.github/workflows/cron-lock.yml
index 935f926cce9..d4172687d54 100644
--- a/.github/workflows/cron-lock.yml
+++ b/.github/workflows/cron-lock.yml
@@ -17,6 +17,11 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea'
steps:
- - uses: dessant/lock-threads@v4
+ - uses: dessant/lock-threads@v5
with:
- issue-inactive-days: 45
+ issue-inactive-days: 10
+ issue-comment: |
+ Automatically locked because of our [CONTRIBUTING guidelines](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#issue-locking)
+ pr-inactive-days: 7
+ pr-comment: |
+ Automatically locked because of our [CONTRIBUTING guidelines](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#issue-locking)
diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml
index e7039053af9..c909f78597d 100644
--- a/.github/workflows/files-changed.yml
+++ b/.github/workflows/files-changed.yml
@@ -35,7 +35,7 @@ jobs:
yaml: ${{ steps.changes.outputs.yaml }}
steps:
- uses: actions/checkout@v4
- - uses: dorny/paths-filter@v2
+ - uses: dorny/paths-filter@v3
id: changes
with:
filters: |
diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml
index 6977dc32b29..391137f015d 100644
--- a/.github/workflows/pull-compliance.yml
+++ b/.github/workflows/pull-compliance.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -64,13 +64,25 @@ jobs:
- run: make deps-frontend
- run: make lint-swagger
+ lint-spell:
+ if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
+ needs: files-changed
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+ check-latest: true
+ - run: make lint-spell
+
lint-go-windows:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -87,7 +99,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -102,7 +114,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -130,7 +142,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -175,7 +187,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml
index 97446e6cd3b..a3886bf6180 100644
--- a/.github/workflows/pull-db-tests.yml
+++ b/.github/workflows/pull-db-tests.yml
@@ -39,7 +39,7 @@ jobs:
- "9000:9000"
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -64,7 +64,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -115,7 +115,7 @@ jobs:
- "9000:9000"
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -165,7 +165,7 @@ jobs:
- "993:993"
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -198,7 +198,7 @@ jobs:
- "1433:1433"
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml
index 540263788d4..5a249db9f8d 100644
--- a/.github/workflows/pull-e2e-tests.yml
+++ b/.github/workflows/pull-e2e-tests.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index ef1e63df2ff..80e6683919f 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -18,7 +18,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -64,7 +64,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -101,7 +101,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml
index 02ec1a1b2dd..12d1e1e4beb 100644
--- a/.github/workflows/release-tag-rc.yml
+++ b/.github/workflows/release-tag-rc.yml
@@ -17,7 +17,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -78,6 +78,8 @@ jobs:
id: meta
with:
images: gitea/gitea
+ flavor: |
+ latest=false
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
@@ -109,6 +111,7 @@ jobs:
images: gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
+ latest=false
suffix=-rootless
# 1.2.3-rc0
tags: |
diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml
index 635507c48d0..e0e93633e8a 100644
--- a/.github/workflows/release-tag-version.yml
+++ b/.github/workflows/release-tag-version.yml
@@ -19,7 +19,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
@@ -86,7 +86,6 @@ jobs:
# 1.2
# 1.2.3
tags: |
- type=raw,value=latest
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
@@ -118,14 +117,13 @@ jobs:
images: gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
- suffix=-rootless
+ suffix=-rootless,onlatest=true
# 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}}
diff --git a/.gitignore b/.gitignore
index 53365ed0b4e..814d9103153 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ _test
.idea
# Goland's output filename can not be set manually
/go_build_*
+/gitea_*
# MS VSCode
.vscode
diff --git a/.gitpod.yml b/.gitpod.yml
index 35b22c45ae7..ed2f57f4bf6 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -10,10 +10,19 @@ tasks:
- name: Run backend
command: |
gp sync-await setup
- if [ ! -f custom/conf/app.ini ]
- then
+
+ # Get the URL and extract the domain
+ url=$(gp url 3000)
+ domain=$(echo $url | awk -F[/:] '{print $4}')
+
+ if [ -f custom/conf/app.ini ]; then
+ sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini
+ sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini
+ sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini
+ sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini
+ else
mkdir -p custom/conf/
- echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini
+ echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
fi
export TAGS="sqlite sqlite_unlock_notify"
diff --git a/.markdownlint.yaml b/.markdownlint.yaml
index 0af8bcd6fe2..b251ff796cb 100644
--- a/.markdownlint.yaml
+++ b/.markdownlint.yaml
@@ -1,17 +1,15 @@
commands-show-output: false
fenced-code-language: false
first-line-h1: false
-header-increment: false
+heading-increment: false
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-emphasis-as-header: false
+no-emphasis-as-heading: false
no-empty-links: false
no-hard-tabs: {code_blocks: false}
no-inline-html: false
no-space-in-code: false
no-space-in-emphasis: false
-no-trailing-punctuation: false
no-trailing-spaces: {br_spaces: 0}
single-h1: false
diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml
index f889a6867c9..7dd0a566f2a 100644
--- a/.stylelintrc.yaml
+++ b/.stylelintrc.yaml
@@ -1,7 +1,7 @@
plugins:
- stylelint-declaration-strict-value
- stylelint-declaration-block-no-ignored-properties
- - stylelint-stylistic
+ - "@stylistic/stylelint-plugin"
ignoreFiles:
- "**/*.go"
@@ -17,12 +17,88 @@ overrides:
customSyntax: postcss-html
rules:
+ "@stylistic/at-rule-name-case": null
+ "@stylistic/at-rule-name-newline-after": null
+ "@stylistic/at-rule-name-space-after": null
+ "@stylistic/at-rule-semicolon-newline-after": null
+ "@stylistic/at-rule-semicolon-space-before": null
+ "@stylistic/block-closing-brace-empty-line-before": null
+ "@stylistic/block-closing-brace-newline-after": null
+ "@stylistic/block-closing-brace-newline-before": null
+ "@stylistic/block-closing-brace-space-after": null
+ "@stylistic/block-closing-brace-space-before": null
+ "@stylistic/block-opening-brace-newline-after": null
+ "@stylistic/block-opening-brace-newline-before": null
+ "@stylistic/block-opening-brace-space-after": null
+ "@stylistic/block-opening-brace-space-before": null
+ "@stylistic/color-hex-case": lower
+ "@stylistic/declaration-bang-space-after": never
+ "@stylistic/declaration-bang-space-before": null
+ "@stylistic/declaration-block-semicolon-newline-after": null
+ "@stylistic/declaration-block-semicolon-newline-before": null
+ "@stylistic/declaration-block-semicolon-space-after": null
+ "@stylistic/declaration-block-semicolon-space-before": never
+ "@stylistic/declaration-block-trailing-semicolon": null
+ "@stylistic/declaration-colon-newline-after": null
+ "@stylistic/declaration-colon-space-after": null
+ "@stylistic/declaration-colon-space-before": never
+ "@stylistic/function-comma-newline-after": null
+ "@stylistic/function-comma-newline-before": null
+ "@stylistic/function-comma-space-after": null
+ "@stylistic/function-comma-space-before": null
+ "@stylistic/function-max-empty-lines": 0
+ "@stylistic/function-parentheses-newline-inside": never-multi-line
+ "@stylistic/function-parentheses-space-inside": null
+ "@stylistic/function-whitespace-after": null
+ "@stylistic/indentation": 2
+ "@stylistic/linebreaks": null
+ "@stylistic/max-empty-lines": 1
+ "@stylistic/max-line-length": null
+ "@stylistic/media-feature-colon-space-after": null
+ "@stylistic/media-feature-colon-space-before": never
+ "@stylistic/media-feature-name-case": null
+ "@stylistic/media-feature-parentheses-space-inside": null
+ "@stylistic/media-feature-range-operator-space-after": always
+ "@stylistic/media-feature-range-operator-space-before": always
+ "@stylistic/media-query-list-comma-newline-after": null
+ "@stylistic/media-query-list-comma-newline-before": null
+ "@stylistic/media-query-list-comma-space-after": null
+ "@stylistic/media-query-list-comma-space-before": null
+ "@stylistic/no-empty-first-line": null
+ "@stylistic/no-eol-whitespace": true
+ "@stylistic/no-extra-semicolons": true
+ "@stylistic/no-missing-end-of-source-newline": null
+ "@stylistic/number-leading-zero": null
+ "@stylistic/number-no-trailing-zeros": null
+ "@stylistic/property-case": lower
+ "@stylistic/selector-attribute-brackets-space-inside": null
+ "@stylistic/selector-attribute-operator-space-after": null
+ "@stylistic/selector-attribute-operator-space-before": null
+ "@stylistic/selector-combinator-space-after": null
+ "@stylistic/selector-combinator-space-before": null
+ "@stylistic/selector-descendant-combinator-no-non-space": null
+ "@stylistic/selector-list-comma-newline-after": null
+ "@stylistic/selector-list-comma-newline-before": null
+ "@stylistic/selector-list-comma-space-after": always-single-line
+ "@stylistic/selector-list-comma-space-before": never-single-line
+ "@stylistic/selector-max-empty-lines": 0
+ "@stylistic/selector-pseudo-class-case": lower
+ "@stylistic/selector-pseudo-class-parentheses-space-inside": never
+ "@stylistic/selector-pseudo-element-case": lower
+ "@stylistic/string-quotes": double
+ "@stylistic/unicode-bom": null
+ "@stylistic/unit-case": lower
+ "@stylistic/value-list-comma-newline-after": null
+ "@stylistic/value-list-comma-newline-before": null
+ "@stylistic/value-list-comma-space-after": null
+ "@stylistic/value-list-comma-space-before": null
+ "@stylistic/value-list-max-empty-lines": 0
alpha-value-notation: null
annotation-no-unknown: true
at-rule-allowed-list: null
at-rule-disallowed-list: null
at-rule-empty-line-before: null
- at-rule-no-unknown: true
+ at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}]
at-rule-no-vendor-prefix: true
at-rule-property-required-list: null
block-no-empty: true
@@ -74,7 +150,7 @@ rules:
keyframe-declaration-no-important: true
keyframe-selector-notation: null
keyframes-name-pattern: null
- length-zero-no-unit: true
+ length-zero-no-unit: [true, ignore: [custom-properties], ignoreFunctions: [var]]
max-nesting-depth: null
media-feature-name-allowed-list: null
media-feature-name-disallowed-list: null
@@ -137,82 +213,6 @@ rules:
selector-type-no-unknown: [true, {ignore: [custom-elements]}]
shorthand-property-no-redundant-values: true
string-no-newline: true
- stylistic/at-rule-name-case: null
- stylistic/at-rule-name-newline-after: null
- stylistic/at-rule-name-space-after: null
- stylistic/at-rule-semicolon-newline-after: null
- stylistic/at-rule-semicolon-space-before: null
- stylistic/block-closing-brace-empty-line-before: null
- stylistic/block-closing-brace-newline-after: null
- stylistic/block-closing-brace-newline-before: null
- stylistic/block-closing-brace-space-after: null
- stylistic/block-closing-brace-space-before: null
- stylistic/block-opening-brace-newline-after: null
- stylistic/block-opening-brace-newline-before: null
- stylistic/block-opening-brace-space-after: null
- stylistic/block-opening-brace-space-before: null
- stylistic/color-hex-case: lower
- stylistic/declaration-bang-space-after: never
- stylistic/declaration-bang-space-before: null
- stylistic/declaration-block-semicolon-newline-after: null
- stylistic/declaration-block-semicolon-newline-before: null
- stylistic/declaration-block-semicolon-space-after: null
- stylistic/declaration-block-semicolon-space-before: never
- stylistic/declaration-block-trailing-semicolon: null
- stylistic/declaration-colon-newline-after: null
- stylistic/declaration-colon-space-after: null
- stylistic/declaration-colon-space-before: never
- stylistic/function-comma-newline-after: null
- stylistic/function-comma-newline-before: null
- stylistic/function-comma-space-after: null
- stylistic/function-comma-space-before: null
- stylistic/function-max-empty-lines: 0
- stylistic/function-parentheses-newline-inside: never-multi-line
- stylistic/function-parentheses-space-inside: null
- stylistic/function-whitespace-after: null
- stylistic/indentation: 2
- stylistic/linebreaks: null
- stylistic/max-empty-lines: 1
- stylistic/max-line-length: null
- stylistic/media-feature-colon-space-after: null
- stylistic/media-feature-colon-space-before: never
- stylistic/media-feature-name-case: null
- stylistic/media-feature-parentheses-space-inside: null
- stylistic/media-feature-range-operator-space-after: always
- stylistic/media-feature-range-operator-space-before: always
- stylistic/media-query-list-comma-newline-after: null
- stylistic/media-query-list-comma-newline-before: null
- stylistic/media-query-list-comma-space-after: null
- stylistic/media-query-list-comma-space-before: null
- stylistic/no-empty-first-line: null
- stylistic/no-eol-whitespace: true
- stylistic/no-extra-semicolons: true
- stylistic/no-missing-end-of-source-newline: null
- stylistic/number-leading-zero: null
- stylistic/number-no-trailing-zeros: null
- stylistic/property-case: lower
- stylistic/selector-attribute-brackets-space-inside: null
- stylistic/selector-attribute-operator-space-after: null
- stylistic/selector-attribute-operator-space-before: null
- stylistic/selector-combinator-space-after: null
- stylistic/selector-combinator-space-before: null
- stylistic/selector-descendant-combinator-no-non-space: null
- stylistic/selector-list-comma-newline-after: null
- stylistic/selector-list-comma-newline-before: null
- stylistic/selector-list-comma-space-after: always-single-line
- stylistic/selector-list-comma-space-before: never-single-line
- stylistic/selector-max-empty-lines: 0
- stylistic/selector-pseudo-class-case: lower
- stylistic/selector-pseudo-class-parentheses-space-inside: never
- stylistic/selector-pseudo-element-case: lower
- stylistic/string-quotes: double
- stylistic/unicode-bom: null
- stylistic/unit-case: lower
- stylistic/value-list-comma-newline-after: null
- stylistic/value-list-comma-newline-before: null
- stylistic/value-list-comma-space-after: null
- stylistic/value-list-comma-space-before: null
- stylistic/value-list-max-empty-lines: 0
time-min-milliseconds: null
unit-allowed-list: null
unit-disallowed-list: null
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae87638f1cc..e119d0bec01 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,240 @@ 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.21.6](https://github.com/go-gitea/gitea/releases/tag/v1.21.6) - 2024-02-22
+
+* SECURITY
+ * Fix XSS vulnerabilities (#29336)
+ * Use general token signing secret (#29205) (#29325)
+* ENHANCEMENTS
+ * Refactor git version functions and check compatibility (#29155) (#29157)
+ * Improve user experience for outdated comments (#29050) (#29086)
+ * Hide code links on release page if user cannot read code (#29064) (#29066)
+ * Wrap contained tags and branches again (#29021) (#29026)
+ * Fix incorrect button CSS usages (#29015) (#29023)
+ * Strip trailing newline in markdown code copy (#29019) (#29022)
+ * Implement some action notifier functions (#29173) (#29308)
+ * Load outdated comments when (un)resolving conversation on PR timeline (#29203) (#29221)
+* BUGFIXES
+ * Refactor issue template parsing and fix API endpoint (#29069) (#29140)
+ * Fix swift packages not resolving (#29095) (#29102)
+ * Remove SSH workaround (#27893) (#29332)
+ * Only log error when tag sync fails (#29295) (#29327)
+ * Fix SSPI user creation (#28948) (#29323)
+ * Improve the `issue_comment` workflow trigger event (#29277) (#29322)
+ * Discard unread data of `git cat-file` (#29297) (#29310)
+ * Fix error display when merging PRs (#29288) (#29309)
+ * Prevent double use of `git cat-file` session. (#29298) (#29301)
+ * Fix missing link on outgoing new release notifications (#29079) (#29300)
+ * Fix debian InRelease Acquire-By-Hash newline (#29204) (#29299)
+ * Always write proc-receive hook for all git versions (#29287) (#29291)
+ * Do not show delete button when time tracker is disabled (#29257) (#29279)
+ * Workaround to clean up old reviews on creating a new one (#28554) (#29264)
+ * Fix bug when the linked account was disactived and list the linked accounts (#29263)
+ * Do not use lower tag names to find releases/tags (#29261) (#29262)
+ * Fix missed edit issues event for actions (#29237) (#29251)
+ * Only delete scheduled workflows when needed (#29091) (#29235)
+ * Make submit event code work with both jQuery event and native event (#29223) (#29234)
+ * Fix push to create with capitalize repo name (#29090) (#29206)
+ * Use ghost user if user was not found (#29161) (#29169)
+ * Dont load Review if Comment is CommentTypeReviewRequest (#28551) (#29160)
+ * Refactor parseSignatureFromCommitLine (#29054) (#29108)
+ * Avoid showing unnecessary JS errors when there are elements with different origin on the page (#29081) (#29089)
+ * Fix gitea-origin-url with default ports (#29085) (#29088)
+ * Fix orgmode link resolving (#29024) (#29076)
+ * Fix Elasticsearh Request Entity Too Large #28117 (#29062) (#29075)
+ * Do not render empty comments (#29039) (#29049)
+ * Avoid sending update/delete release notice when it is draft (#29008) (#29025)
+ * Fix gitea-action user avatar broken on edited menu (#29190) (#29307)
+ * Disallow merge when required checked are missing (#29143) (#29268)
+ * Fix incorrect link to swift doc and swift package-registry login command (#29096) (#29103)
+ * Convert visibility to number (#29226) (#29244)
+* DOCS
+ * Remove outdated docs from some languages (#27530) (#29208)
+ * Fix typos in the documentation (#29048) (#29056)
+ * Explained where create issue/PR template (#29035)
+
+## [1.21.5](https://github.com/go-gitea/gitea/releases/tag/v1.21.5) - 2024-01-31
+
+* SECURITY
+ * Prevent anonymous container access if `RequireSignInView` is enabled (#28877) (#28882)
+ * Update go dependencies and fix go-git (#28893) (#28934)
+* BUGFIXES
+ * Revert "Speed up loading the dashboard on mysql/mariadb (#28546)" (#29006) (#29007)
+ * Fix an actions schedule bug (#28942) (#28999)
+ * Fix update enable_prune even if mirror_interval is not provided (#28905) (#28929)
+ * Fix uploaded artifacts should be overwritten (#28726) backport v1.21 (#28832)
+ * Preserve BOM in web editor (#28935) (#28959)
+ * Strip `/` from relative links (#28932) (#28952)
+ * Don't remove all mirror repository's releases when mirroring (#28817) (#28939)
+ * Implement `MigrateRepository` for the actions notifier (#28920) (#28923)
+ * Respect branch info for relative links (#28909) (#28922)
+ * Don't reload timeline page when (un)resolving or replying conversation (#28654) (#28917)
+ * Only migrate the first 255 chars of a Github issue title (#28902) (#28912)
+ * Fix sort bug on repository issues list (#28897) (#28901)
+ * Fix `DeleteCollaboration` transaction behaviour (#28886) (#28889)
+ * Fix schedule not trigger bug because matching full ref name with short ref name (#28874) (#28888)
+ * Fix migrate storage bug (#28830) (#28867)
+ * Fix archive creating LFS hooks and breaking pull requests (#28848) (#28851)
+ * Fix reverting a merge commit failing (#28794) (#28825)
+ * Upgrade xorm to v1.3.7 to fix a resource leak problem caused by Iterate (#28891) (#28895)
+ * Fix incorrect PostgreSQL connection string for Unix sockets (#28865) (#28870)
+* ENHANCEMENTS
+ * Make loading animation less aggressive (#28955) (#28956)
+ * Avoid duplicate JS error messages on UI (#28873) (#28881)
+ * Bump `@github/relative-time-element` to 4.3.1 (#28819) (#28826)
+* MISC
+ * Warn that `DISABLE_QUERY_AUTH_TOKEN` is false only if it's explicitly defined (#28783) (#28868)
+ * Remove duplicated checkinit on git module (#28824) (#28831)
+
+## [1.21.4](https://github.com/go-gitea/gitea/releases/tag/v1.21.4) - 2024-01-16
+
+* SECURITY
+ * Update github.com/cloudflare/circl (#28789) (#28790)
+ * Require token for GET subscription endpoint (#28765) (#28768)
+* BUGFIXES
+ * Use refname:strip-2 instead of refname:short when syncing tags (#28797) (#28811)
+ * Fix links in issue card (#28806) (#28807)
+ * Fix nil pointer panic when exec some gitea cli command (#28791) (#28795)
+ * Require token for GET subscription endpoint (#28765) (#28778)
+ * Fix button size in "attached header right" (#28770) (#28774)
+ * Fix `convert.ToTeams` on empty input (#28426) (#28767)
+ * Hide code related setting options in repository when code unit is disabled (#28631) (#28749)
+ * Fix incorrect URL for "Reference in New Issue" (#28716) (#28723)
+ * Fix panic when parsing empty pgsql host (#28708) (#28709)
+ * Upgrade xorm to new version which supported update join for all supported databases (#28590) (#28668)
+ * Fix alpine package files are not rebuilt (#28638) (#28665)
+ * Avoid cycle-redirecting user/login page (#28636) (#28658)
+ * Fix empty ref for cron workflow runs (#28640) (#28647)
+ * Remove unnecessary syncbranchToDB with tests (#28624) (#28629)
+ * Use known issue IID to generate new PR index number when migrating from GitLab (#28616) (#28618)
+ * Fix flex container width (#28603) (#28605)
+ * Fix the scroll behavior for emoji/mention list (#28597) (#28601)
+ * Fix wrong due date rendering in issue list page (#28588) (#28591)
+ * Fix `status_check_contexts` matching bug (#28582) (#28589)
+ * Fix 500 error of searching commits (#28576) (#28579)
+ * Use information from previous blame parts (#28572) (#28577)
+ * Update mermaid for 1.21 (#28571)
+ * Fix 405 method not allowed CORS / OIDC (#28583) (#28586) (#28587) (#28611)
+ * Fix `GetCommitStatuses` (#28787) (#28804)
+ * Forbid removing the last admin user (#28337) (#28793)
+ * Fix schedule tasks bugs (#28691) (#28780)
+ * Fix issue dependencies (#27736) (#28776)
+ * Fix system webhooks API bug (#28531) (#28666)
+ * Fix when private user following user, private user will not be counted in his own view (#28037) (#28792)
+ * Render code block in activity tab (#28816) (#28818)
+* ENHANCEMENTS
+ * Rework markup link rendering (#26745) (#28803)
+ * Modernize merge button (#28140) (#28786)
+ * Speed up loading the dashboard on mysql/mariadb (#28546) (#28784)
+ * Assign pull request to project during creation (#28227) (#28775)
+ * Show description as tooltip instead of title for labels (#28754) (#28766)
+ * Make template `DateTime` show proper tooltip (#28677) (#28683)
+ * Switch destination directory for apt signing keys (#28639) (#28642)
+ * Include heap pprof in diagnosis report to help debugging memory leaks (#28596) (#28599)
+* DOCS
+ * Suggest to use Type=simple for systemd service (#28717) (#28722)
+ * Extend description for ARTIFACT_RETENTION_DAYS (#28626) (#28630)
+* MISC
+ * Add -F to commit search to treat keywords as strings (#28744) (#28748)
+ * Add download attribute to release attachments (#28739) (#28740)
+ * Concatenate error in `checkIfPRContentChanged` (#28731) (#28737)
+ * Improve 1.21 document for Database Preparation (#28643) (#28644)
+
+## [1.21.3](https://github.com/go-gitea/gitea/releases/tag/v1.21.3) - 2023-12-21
+
+* SECURITY
+ * Update golang.org/x/crypto (#28519)
+* API
+ * chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525)
+ * Add endpoint for not implemented Docker auth (#28457) (#28462)
+* ENHANCEMENTS
+ * Add option to disable ambiguous unicode characters detection (#28454) (#28499)
+ * Refactor SSH clone URL generation code (#28421) (#28480)
+ * Polyfill SubmitEvent for PaleMoon (#28441) (#28478)
+* BUGFIXES
+ * Fix the issue ref rendering for wiki (#28556) (#28559)
+ * Fix duplicate ID when deleting repo (#28520) (#28528)
+ * Only check online runner when detecting matching runners in workflows (#28286) (#28512)
+ * Initalize stroage for orphaned repository doctor (#28487) (#28490)
+ * Fix possible nil pointer access (#28428) (#28440)
+ * Don't show unnecessary citation JS error on UI (#28433) (#28437)
+* DOCS
+ * Update actions document about comparsion as Github Actions (#28560) (#28564)
+ * Fix documents for "custom/public/assets/" (#28465) (#28467)
+* MISC
+ * Fix inperformant query on retrifing review from database. (#28552) (#28562)
+ * Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
+ * Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488)
+ * Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
+ * Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
+
+## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/v1.21.2) - 2023-12-12
+
+* SECURITY
+ * Rebuild with recently released golang version
+ * Fix missing check (#28406) (#28411)
+ * Do some missing checks (#28423) (#28432)
+* BUGFIXES
+ * Fix margin in server signed signature verification view (#28379) (#28381)
+ * Fix object does not exist error when checking citation file (#28314) (#28369)
+ * Use `filepath` instead of `path` to create SQLite3 database file (#28374) (#28378)
+ * Fix the runs will not be displayed bug when the main branch have no workflows but other branches have (#28359) (#28365)
+ * Handle repository.size column being NULL in migration v263 (#28336) (#28363)
+ * Convert git commit summary to valid UTF8. (#28356) (#28358)
+ * Fix migration panic due to an empty review comment diff (#28334) (#28362)
+ * Add `HEAD` support for rpm repo files (#28309) (#28360)
+ * Fix RPM/Debian signature key creation (#28352) (#28353)
+ * Keep profile tab when clicking on Language (#28320) (#28331)
+ * Fix missing issue search index update when changing status (#28325) (#28330)
+ * Fix wrong link in `protect_branch_name_pattern_desc` (#28313) (#28315)
+ * Read `previous` info from git blame (#28306) (#28310)
+ * Ignore "non-existing" errors when getDirectorySize calculates the size (#28276) (#28285)
+ * Use appSubUrl for OAuth2 callback URL tip (#28266) (#28275)
+ * Meilisearch: require all query terms to be matched (#28293) (#28296)
+ * Fix required error for token name (#28267) (#28284)
+ * Fix issue will be detected as pull request when checking `First-time contributor` (#28237) (#28271)
+ * Use full width for project boards (#28225) (#28245)
+ * Increase "version" when update the setting value to a same value as before (#28243) (#28244)
+ * Also sync DB branches on push if necessary (#28361) (#28403)
+ * Make gogit Repository.GetBranchNames consistent (#28348) (#28386)
+ * Recover from panic in cron task (#28409) (#28425)
+ * Deprecate query string auth tokens (#28390) (#28430)
+* ENHANCEMENTS
+ * Improve doctor cli behavior (#28422) (#28424)
+ * Fix margin in server signed signature verification view (#28379) (#28381)
+ * Refactor template empty checks (#28351) (#28354)
+ * Read `previous` info from git blame (#28306) (#28310)
+ * Use full width for project boards (#28225) (#28245)
+ * Enable system users search via the API (#28013) (#28018)
+
+## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/v1.21.1) - 2023-11-26
+
+* SECURITY
+ * Fix comment permissions (#28213) (#28216)
+* BUGFIXES
+ * Fix delete-orphaned-repos (#28200) (#28202)
+ * Make CORS work for oauth2 handlers (#28184) (#28185)
+ * Fix missing buttons (#28179) (#28181)
+ * Fix no ActionTaskOutput table waring (#28149) (#28152)
+ * Fix empty action run title (#28113) (#28148)
+ * Use "is-loading" to avoid duplicate form submit for code comment (#28143) (#28147)
+ * Fix Matrix and MSTeams nil dereference (#28089) (#28105)
+ * Fix incorrect pgsql conn builder behavior (#28085) (#28098)
+ * Fix system config cache expiration timing (#28072) (#28090)
+ * Restricted users only see repos in orgs which their team was assigned to (#28025) (#28051)
+* API
+ * Fix permissions for Token DELETE endpoint to match GET and POST (#27610) (#28099)
+* ENHANCEMENTS
+ * Do not display search box when there's no packages yet (#28146) (#28159)
+ * Add missing `packages.cleanup.success` (#28129) (#28132)
+* DOCS
+ * Docs: Replace deprecated IS_TLS_ENABLED mailer setting in email setup (#28205) (#28208)
+ * Fix the description about the default setting for action in quick start document (#28160) (#28168)
+ * Add guide page to actions when there's no workflows (#28145) (#28153)
+* MISC
+ * Use full width for PR comparison (#28182) (#28186)
+
## [1.21.0](https://github.com/go-gitea/gitea/releases/tag/v1.21.0) - 2023-11-14
* BREAKING
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b43ce01ff24..dc90c6905b9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,6 +8,7 @@
- [How to report issues](#how-to-report-issues)
- [Types of issues](#types-of-issues)
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
+ - [Issue locking](#issue-locking)
- [Building Gitea](#building-gitea)
- [Dependencies](#dependencies)
- [Backend](#backend)
@@ -47,6 +48,7 @@
- [Release Cycle](#release-cycle)
- [Maintainers](#maintainers)
- [Technical Oversight Committee (TOC)](#technical-oversight-committee-toc)
+ - [TOC election process](#toc-election-process)
- [Current TOC members](#current-toc-members)
- [Previous TOC/owners members](#previous-tocowners-members)
- [Governance Compensation](#governance-compensation)
@@ -102,6 +104,13 @@ the goals for the project and tools.
Pull requests should not be the place for architecture discussions.
+### Issue locking
+
+Commenting on closed or merged issues/PRs is strongly discouraged.
+Such comments will likely be overlooked as some maintainers may not view notifications on closed issues, thinking that the item is resolved.
+As such, commenting on closed/merged issues/PRs may be disabled prior to the scheduled auto-locking if a discussion starts or if unrelated comments are posted.
+If further discussion is needed, we encourage you to open a new issue instead and we recommend linking to the issue/PR in question for context.
+
## Building Gitea
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
@@ -110,7 +119,7 @@ See the [development setup instructions](https://docs.gitea.com/development/hack
### Backend
-Go dependencies are managed using [Go Modules](https://golang.org/cmd/go/#hdr-Module_maintenance). \
+Go dependencies are managed using [Go Modules](https://go.dev/cmd/go/#hdr-Module_maintenance). \
You can find more details in the [go mod documentation](https://go.dev/ref/mod) and the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules).
Pull requests should only modify `go.mod` and `go.sum` where it is related to your change, be it a bugfix or a new feature. \
@@ -167,7 +176,7 @@ Here's how to run the test suite:
| Command | Action | |
| :------------------------------------- | :----------------------------------------------- | ------------ |
-|``make test[\#SpecificTestName]`` | run unit test(s) |
+|``make test[\#SpecificTestName]`` | run unit test(s) | |
|``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) |
|``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/README.md) |
@@ -486,36 +495,53 @@ if possible provide GPG signed commits.
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
https://help.github.com/articles/signing-commits-with-gpg/
+Furthermore, any account with write access (like bots and TOC members) **must** use 2FA.
+https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
+
## Technical Oversight Committee (TOC)
-At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions would be elected as it has been over the past years, and the other three would consist of appointed members from the Gitea company.
+At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions are elected as it has been over the past years, and the other three consist of appointed members from the Gitea company.
https://blog.gitea.com/quarterly-23q1/
-When the new community members have been elected, the old members will give up ownership to the newly elected members. For security reasons, TOC members or any account with write access (like a bot) must use 2FA.
-https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
+### TOC election process
+
+Any maintainer is eligible to be part of the community TOC if they are not associated with the Gitea company.
+A maintainer can either nominate themselves, or can be nominated by other maintainers to be a candidate for the TOC election.
+If you are nominated by someone else, you must first accept your nomination before the vote starts to be a candidate.
+
+The TOC is elected for one year, the TOC election happens yearly.
+After the announcement of the results of the TOC election, elected members have two weeks time to confirm or refuse the seat.
+If an elected member does not answer within this timeframe, they are automatically assumed to refuse the seat.
+Refusals result in the person with the next highest vote getting the same choice.
+As long as seats are empty in the TOC, members of the previous TOC can fill them until an elected member accepts the seat.
+
+If an elected member that accepts the seat does not have 2FA configured yet, they will be temporarily counted as `answer pending` until they manage to configure 2FA, thus leaving their seat empty for this duration.
### Current TOC members
-- 2023-01-01 ~ 2023-12-31 - https://blog.gitea.com/quarterly-23q1/
+- 2024-01-01 ~ 2024-12-31
- Company
- [Jason Song](https://gitea.com/wolfogre)
- [Lunny Xiao](https://gitea.com/lunny)
- - [Matti Ranta](https://gitea.com/techknowlogick)
+ - [Matti Ranta](https://gitea.com/techknowlogick)
- Community
- [6543](https://gitea.com/6543) <6543@obermui.de>
- - [Andrew Thornton](https://gitea.com/zeripath)
+ - [delvh](https://gitea.com/delvh)
- [John Olheiser](https://gitea.com/jolheiser)
### Previous TOC/owners members
Here's the history of the owners and the time they served:
-- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872)
+- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
- [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017
- [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801)
-- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872)
-- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872)
+- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
+- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
+- [6543](https://gitea.com/6543) - 2023
+- [John Olheiser](https://gitea.com/jolheiser) - 2023
+- [Jason Song](https://gitea.com/wolfogre) - 2023
## Governance Compensation
diff --git a/Dockerfile b/Dockerfile
index 5fe8df91268..b647c0cd590 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Build stage
-FROM docker.io/library/golang:1.21-alpine3.18 AS build-env
+FROM docker.io/library/golang:1.22-alpine3.19 AS build-env
ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct}
@@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
-FROM docker.io/library/alpine:3.18
+FROM docker.io/library/alpine:3.19
LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000
diff --git a/Dockerfile.rootless b/Dockerfile.rootless
index 5ea4d2fc751..dd7da972783 100644
--- a/Dockerfile.rootless
+++ b/Dockerfile.rootless
@@ -1,5 +1,5 @@
# Build stage
-FROM docker.io/library/golang:1.21-alpine3.18 AS build-env
+FROM docker.io/library/golang:1.22-alpine3.19 AS build-env
ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct}
@@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
-FROM docker.io/library/alpine:3.18
+FROM docker.io/library/alpine:3.19
LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000
diff --git a/MAINTAINERS b/MAINTAINERS
index 72171f80ed4..2f95fdca50b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -59,3 +59,4 @@ Rui Chen (@chenrui333)
Nanguan Lin (@lng2020)
kerwin612 (@kerwin612)
Gary Wang (@BLumia)
+Tim-Niclas Oelschläger (@zokkis)
diff --git a/Makefile b/Makefile
index 068dda5f52b..0e9e7920539 100644
--- a/Makefile
+++ b/Makefile
@@ -23,19 +23,19 @@ SHASUM ?= shasum -a 256
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
COMMA := ,
-XGO_VERSION := go-1.21.x
+XGO_VERSION := go-1.22.x
-AIR_PACKAGE ?= github.com/cosmtrek/air@v1.44.0
+AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
-GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.5.0
-GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.0
+GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
+GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
-MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
+MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
-GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.1
-ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.25
+GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3
+ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.26
DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest
@@ -119,7 +119,7 @@ GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/m
FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
-WEBPACK_CONFIGS := webpack.config.js
+WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
@@ -146,6 +146,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css
+SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
+
GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
GO_SOURCES += $(GENERATED_GO_DEST)
@@ -162,8 +164,8 @@ ifdef DEPS_PLAYWRIGHT
endif
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
-SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
-SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
+SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
+SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_EXCLUDE := code.gitea.io/sdk
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
@@ -219,6 +221,8 @@ help:
@echo " - lint-swagger lint swagger files"
@echo " - lint-templates lint template files"
@echo " - lint-yaml lint yaml files"
+ @echo " - lint-spell lint spelling"
+ @echo " - lint-spell-fix lint spelling and fix issues"
@echo " - checks run various consistency checks"
@echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files"
@@ -308,10 +312,6 @@ fmt-check: fmt
exit 1; \
fi
-.PHONY: misspell-check
-misspell-check:
- go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS)
-
.PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR)
@@ -351,13 +351,13 @@ checks: checks-frontend checks-backend
checks-frontend: lockfile-check svg-check
.PHONY: checks-backend
-checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-validate security-check
+checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
.PHONY: lint
-lint: lint-frontend lint-backend
+lint: lint-frontend lint-backend lint-spell
.PHONY: lint-fix
-lint-fix: lint-frontend-fix lint-backend-fix
+lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix
.PHONY: lint-frontend
lint-frontend: lint-js lint-css
@@ -395,6 +395,14 @@ lint-swagger: node_modules
lint-md: node_modules
npx markdownlint docs *.md
+.PHONY: lint-spell
+lint-spell:
+ @go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES)
+
+.PHONY: lint-spell-fix
+lint-spell-fix:
+ @go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)
+
.PHONY: lint-go
lint-go:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run
@@ -594,8 +602,7 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright
-playwright: $(PLAYWRIGHT_DIR)
- npm install --no-save @playwright/test
+playwright: deps-frontend
npx playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e%
@@ -962,7 +969,7 @@ generate-gitignore:
.PHONY: generate-images
generate-images: | node_modules
- npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7
+ npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7
node build/generate-images.js $(TAGS)
.PHONY: generate-manpage
@@ -980,3 +987,8 @@ docker:
# This endif closes the if at the top of the file
endif
+
+# Disable parallel execution because it would break some targets that don't
+# specify exact dependencies like 'backend' which does currently not depend
+# on 'frontend' to enable Node.js-less builds from source tarballs.
+.NOTPARALLEL:
diff --git a/README.md b/README.md
index dc396465a7e..94d7284c7cb 100644
--- a/README.md
+++ b/README.md
@@ -45,9 +45,6 @@
-
-
-
@@ -62,11 +59,16 @@ painless way of setting up a self-hosted Git service.
As Gitea is written in Go, it works across **all** the platforms and
architectures that are supported by Go, including Linux, macOS, and
Windows on x86, amd64, ARM and PowerPC architectures.
-You can try it out using [the online demo](https://try.gitea.io/).
This project has been
[forked](https://blog.gitea.com/welcome-to-gitea/) from
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
+For online demonstrations, you can visit [try.gitea.io](https://try.gitea.io).
+
+For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
+
+To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
+
## Building
From the root of the source tree, run:
@@ -84,25 +86,23 @@ The `build` target is split into two sub-targets:
Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js.
-Parallelism (`make -j `) is not supported.
-
More info: https://docs.gitea.com/installation/install-from-source
## Using
./gitea web
-NOTE: If you're interested in using our APIs, we have experimental
-support with [documentation](https://try.gitea.io/api/swagger).
+> [!NOTE]
+> If you're interested in using our APIs, we have experimental support with [documentation](https://try.gitea.io/api/swagger).
## Contributing
Expected workflow is: Fork -> Patch -> Push -> Pull Request
-NOTES:
-
-1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
-2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
+> [!NOTE]
+>
+> 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
+> 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
## Translating
@@ -173,5 +173,5 @@ Looking for an overview of the interface? Check it out!
||||
|:---:|:---:|:---:|
||||
-|||
-|||
+||||
+||||
diff --git a/README_ZH.md b/README_ZH.md
index dd77c05de8f..adfeb9a8df7 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -45,9 +45,6 @@
-
-
-
@@ -58,7 +55,11 @@
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86,amd64,还包括 ARM 和 PowerPC。
-如果您想试用一下,请访问 [在线Demo](https://try.gitea.io/)!
+如果你想试用在线演示,请访问 [try.gitea.io](https://try.gitea.io/)。
+
+如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。
+
+如果你想在 Gitea Cloud 上快速部署你自己独享的 Gitea 实例,请访问 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
## 提示
@@ -94,5 +95,5 @@ Fork -> Patch -> Push -> Pull Request
||||
|:---:|:---:|:---:|
||||
-|||
-|||
+||||
+||||
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index ee1e503f2a6..2aa60780c48 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -545,8 +545,8 @@
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
- "name": "github.com/google/go-github/v53/github",
- "path": "github.com/google/go-github/v53/github/LICENSE",
+ "name": "github.com/google/go-github/v57/github",
+ "path": "github.com/google/go-github/v57/github/LICENSE",
"licenseText": "Copyright (c) 2013 The go-github AUTHORS. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
@@ -572,27 +572,27 @@
{
"name": "github.com/gorilla/css/scanner",
"path": "github.com/gorilla/css/scanner/LICENSE",
- "licenseText": "Copyright (c) 2013, Gorilla web toolkit\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n Redistributions in binary form must reproduce the above copyright notice, this\n list of conditions and the following disclaimer in the documentation and/or\n other materials provided with the distribution.\n\n Neither the name of the {organization} nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
+ "licenseText": "Copyright (c) 2023 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\n"
},
{
"name": "github.com/gorilla/feeds",
"path": "github.com/gorilla/feeds/LICENSE",
- "licenseText": "Copyright (c) 2013-2018 The Gorilla Feeds 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 met:\n\n Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (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"
+ "licenseText": "Copyright (c) 2023 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\n"
},
{
"name": "github.com/gorilla/mux",
"path": "github.com/gorilla/mux/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"
+ "licenseText": "Copyright (c) 2023 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/gorilla/securecookie",
"path": "github.com/gorilla/securecookie/LICENSE",
- "licenseText": "Copyright (c) 2012 Rodrigo Moraes. 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"
+ "licenseText": "Copyright (c) 2023 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/gorilla/sessions",
"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"
+ "licenseText": "Copyright (c) 2023 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/go-cleanhttp",
@@ -734,15 +734,10 @@
"path": "github.com/mattn/go-runewidth/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Yasuhiro Matsumoto\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/matttproud/golang_protobuf_extensions/pbutil",
- "path": "github.com/matttproud/golang_protobuf_extensions/pbutil/LICENSE",
- "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
- },
{
"name": "github.com/meilisearch/meilisearch-go",
"path": "github.com/meilisearch/meilisearch-go/LICENSE",
- "licenseText": "MIT License\n\nCopyright (c) 2020-2022 Meili SAS\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"
+ "licenseText": "MIT License\n\nCopyright (c) 2020-2024 Meili SAS\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/mholt/acmez",
diff --git a/build/generate-images.js b/build/generate-images.js
index a3a0f8d8f39..db31d19e2a4 100755
--- a/build/generate-images.js
+++ b/build/generate-images.js
@@ -1,20 +1,13 @@
#!/usr/bin/env node
import imageminZopfli from 'imagemin-zopfli';
import {optimize} from 'svgo';
-import {fabric} from 'fabric';
+import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node';
import {readFile, writeFile} from 'node:fs/promises';
+import {argv, exit} from 'node:process';
-function exit(err) {
+function doExit(err) {
if (err) console.error(err);
- process.exit(err ? 1 : 0);
-}
-
-function loadSvg(svg) {
- return new Promise((resolve) => {
- fabric.loadSVGFromString(svg, (objects, options) => {
- resolve({objects, options});
- });
- });
+ exit(err ? 1 : 0);
}
async function generate(svg, path, {size, bg}) {
@@ -35,14 +28,14 @@ async function generate(svg, path, {size, bg}) {
return;
}
- const {objects, options} = await loadSvg(svg);
- const canvas = new fabric.Canvas();
+ const {objects, options} = await loadSVGFromString(svg);
+ const canvas = new Canvas();
canvas.setDimensions({width: size, height: size});
const ctx = canvas.getContext('2d');
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
if (bg) {
- canvas.add(new fabric.Rect({
+ canvas.add(new Rect({
left: 0,
top: 0,
height: size * (1 / (size / options.height)),
@@ -51,7 +44,7 @@ async function generate(svg, path, {size, bg}) {
}));
}
- canvas.add(fabric.util.groupSVGElements(objects, options));
+ canvas.add(util.groupSVGElements(objects, options));
canvas.renderAll();
let png = Buffer.from([]);
@@ -64,7 +57,7 @@ async function generate(svg, path, {size, bg}) {
}
async function main() {
- const gitea = process.argv.slice(2).includes('gitea');
+ const gitea = argv.slice(2).includes('gitea');
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
@@ -79,4 +72,8 @@ async function main() {
]);
}
-main().then(exit).catch(exit);
+try {
+ doExit(await main());
+} catch (err) {
+ doExit(err);
+}
diff --git a/build/generate-svg.js b/build/generate-svg.js
index b845da9367c..f26b60d9607 100755
--- a/build/generate-svg.js
+++ b/build/generate-svg.js
@@ -4,15 +4,16 @@ import {optimize} from 'svgo';
import {parse} from 'node:path';
import {readFile, writeFile, mkdir} from 'node:fs/promises';
import {fileURLToPath} from 'node:url';
+import {exit} from 'node:process';
const glob = (pattern) => fastGlob.sync(pattern, {
cwd: fileURLToPath(new URL('..', import.meta.url)),
absolute: true,
});
-function exit(err) {
+function doExit(err) {
if (err) console.error(err);
- process.exit(err ? 1 : 0);
+ exit(err ? 1 : 0);
}
async function processFile(file, {prefix, fullName} = {}) {
@@ -63,4 +64,8 @@ async function main() {
]);
}
-main().then(exit).catch(exit);
+try {
+ doExit(await main());
+} catch (err) {
+ doExit(err);
+}
diff --git a/cmd/actions.go b/cmd/actions.go
index 052afb9ebc3..f582c16c81c 100644
--- a/cmd/actions.go
+++ b/cmd/actions.go
@@ -15,9 +15,8 @@ import (
var (
// CmdActions represents the available actions sub-commands.
CmdActions = &cli.Command{
- Name: "actions",
- Usage: "",
- Description: "Commands for managing Gitea Actions",
+ Name: "actions",
+ Usage: "Manage Gitea Actions",
Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken,
},
@@ -51,6 +50,6 @@ func runGenerateActionsRunnerToken(c *cli.Context) error {
if extra.HasError() {
return handleCliResponseExtra(extra)
}
- _, _ = fmt.Printf("%s\n", respText)
+ _, _ = fmt.Printf("%s\n", respText.Text)
return nil
}
diff --git a/cmd/admin.go b/cmd/admin.go
index 49d0e4ef74a..6c9480e76eb 100644
--- a/cmd/admin.go
+++ b/cmd/admin.go
@@ -11,6 +11,7 @@ import (
"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/gitrepo"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
@@ -21,7 +22,7 @@ var (
// CmdAdmin represents the available admin sub-command.
CmdAdmin = &cli.Command{
Name: "admin",
- Usage: "Command line interface to perform common administrative operations",
+ Usage: "Perform common administrative operations",
Subcommands: []*cli.Command{
subcmdUser,
subcmdRepoSyncReleases,
@@ -122,7 +123,7 @@ func runRepoSyncReleases(_ *cli.Context) error {
log.Trace("Processing next %d repos of %d", len(repos), count)
for _, repo := range repos {
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
- gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
log.Warn("OpenRepository: %v", err)
continue
@@ -157,10 +158,10 @@ func runRepoSyncReleases(_ *cli.Context) error {
}
func getReleaseCount(ctx context.Context, id int64) (int64, error) {
- return repo_model.GetReleaseCountByRepoID(
+ return db.Count[repo_model.Release](
ctx,
- id,
repo_model.FindReleasesOptions{
+ RepoID: id,
IncludeTags: true,
},
)
diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go
index eebbfb3b671..824d66d1125 100644
--- a/cmd/admin_user_change_password.go
+++ b/cmd/admin_user_change_password.go
@@ -4,13 +4,14 @@
package cmd
import (
- "context"
"errors"
"fmt"
user_model "code.gitea.io/gitea/models/user"
- pwd "code.gitea.io/gitea/modules/auth/password"
+ "code.gitea.io/gitea/modules/auth/password"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v2"
)
@@ -32,6 +33,10 @@ var microcmdUserChangePassword = &cli.Command{
Value: "",
Usage: "New password to set for user",
},
+ &cli.BoolFlag{
+ Name: "must-change-password",
+ Usage: "User must change password",
+ },
},
}
@@ -46,31 +51,32 @@ func runChangePassword(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
- if len(c.String("password")) < setting.MinPasswordLength {
- return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
- }
- if !pwd.IsComplexEnough(c.String("password")) {
- return errors.New("Password does not meet complexity requirements")
- }
- pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
+ user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
- if pwned {
- return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
- }
- uname := c.String("username")
- user, err := user_model.GetUserByName(ctx, uname)
- if err != nil {
- return err
- }
- if err = user.SetPassword(c.String("password")); err != nil {
- return err
+
+ var mustChangePassword optional.Option[bool]
+ if c.IsSet("must-change-password") {
+ mustChangePassword = optional.Some(c.Bool("must-change-password"))
}
- if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
- return err
+ opts := &user_service.UpdateAuthOptions{
+ Password: optional.Some(c.String("password")),
+ MustChangePassword: mustChangePassword,
+ }
+ if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
+ switch {
+ case errors.Is(err, password.ErrMinLength):
+ return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
+ case errors.Is(err, password.ErrComplexity):
+ return errors.New("Password does not meet complexity requirements")
+ case errors.Is(err, password.ErrIsPwned):
+ return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
+ default:
+ return err
+ }
}
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go
index fefe18d39c5..a257ce21c8d 100644
--- a/cmd/admin_user_create.go
+++ b/cmd/admin_user_create.go
@@ -10,8 +10,8 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v2"
)
@@ -123,10 +123,10 @@ func runCreateUser(c *cli.Context) error {
changePassword = c.Bool("must-change-password")
}
- restricted := util.OptionalBoolNone
+ restricted := optional.None[bool]()
if c.IsSet("restricted") {
- restricted = util.OptionalBoolOf(c.Bool("restricted"))
+ restricted = optional.Some(c.Bool("restricted"))
}
// default user visibility in app.ini
@@ -142,7 +142,7 @@ func runCreateUser(c *cli.Context) error {
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
IsRestricted: restricted,
}
diff --git a/cmd/doctor.go b/cmd/doctor.go
index d040a3af1c8..e433f4adc5d 100644
--- a/cmd/doctor.go
+++ b/cmd/doctor.go
@@ -14,14 +14,28 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/migrations"
migrate_base "code.gitea.io/gitea/models/migrations/base"
- "code.gitea.io/gitea/modules/doctor"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/doctor"
"github.com/urfave/cli/v2"
"xorm.io/xorm"
)
+// CmdDoctor represents the available doctor sub-command.
+var CmdDoctor = &cli.Command{
+ Name: "doctor",
+ Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
+ Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
+
+ Subcommands: []*cli.Command{
+ cmdDoctorCheck,
+ cmdRecreateTable,
+ cmdDoctorConvert,
+ },
+}
+
var cmdDoctorCheck = &cli.Command{
Name: "check",
Usage: "Diagnose and optionally fix problems",
@@ -60,19 +74,6 @@ var cmdDoctorCheck = &cli.Command{
},
}
-// CmdDoctor represents the available doctor sub-command.
-var CmdDoctor = &cli.Command{
- Name: "doctor",
- Usage: "Diagnose and optionally fix problems",
- Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
-
- Subcommands: []*cli.Command{
- cmdDoctorCheck,
- cmdRecreateTable,
- cmdDoctorConvert,
- },
-}
-
var cmdRecreateTable = &cli.Command{
Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.",
@@ -177,6 +178,7 @@ func runDoctorCheck(ctx *cli.Context) error {
if ctx.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
+ doctor.SortChecks(doctor.Checks)
for _, check := range doctor.Checks {
if check.IsDefault {
_, _ = w.Write([]byte{'*'})
@@ -192,26 +194,20 @@ func runDoctorCheck(ctx *cli.Context) error {
var checks []*doctor.Check
if ctx.Bool("all") {
- checks = doctor.Checks
+ checks = make([]*doctor.Check, len(doctor.Checks))
+ copy(checks, doctor.Checks)
} else if ctx.IsSet("run") {
addDefault := ctx.Bool("default")
- names := ctx.StringSlice("run")
- for i, name := range names {
- names[i] = strings.ToLower(strings.TrimSpace(name))
- }
-
+ runNamesSet := container.SetOf(ctx.StringSlice("run")...)
for _, check := range doctor.Checks {
- if addDefault && check.IsDefault {
+ if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check)
- continue
- }
- for _, name := range names {
- if name == check.Name {
- checks = append(checks, check)
- break
- }
+ runNamesSet.Remove(check.Name)
}
}
+ if len(runNamesSet) > 0 {
+ return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ","))
+ }
} else {
for _, check := range doctor.Checks {
if check.IsDefault {
@@ -219,6 +215,5 @@ func runDoctorCheck(ctx *cli.Context) error {
}
}
}
-
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
}
diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go
index 2385f23e521..48c835ad0e2 100644
--- a/cmd/doctor_convert.go
+++ b/cmd/doctor_convert.go
@@ -37,8 +37,8 @@ func runDoctorConvert(ctx *cli.Context) error {
switch {
case setting.Database.Type.IsMySQL():
- if err := db.ConvertUtf8ToUtf8mb4(); err != nil {
- log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err)
+ if err := db.ConvertDatabaseTable(); err != nil {
+ log.Fatal("Failed to convert database & table: %v", err)
return err
}
fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4")
diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go
new file mode 100644
index 00000000000..3e1ff299c5a
--- /dev/null
+++ b/cmd/doctor_test.go
@@ -0,0 +1,33 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "context"
+ "testing"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/doctor"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/urfave/cli/v2"
+)
+
+func TestDoctorRun(t *testing.T) {
+ doctor.Register(&doctor.Check{
+ Title: "Test Check",
+ Name: "test-check",
+ Run: func(ctx context.Context, logger log.Logger, autofix bool) error { return nil },
+
+ SkipDatabaseInitialization: true,
+ })
+ app := cli.NewApp()
+ app.Commands = []*cli.Command{cmdDoctorCheck}
+ err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
+ assert.NoError(t, err)
+ err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
+ assert.ErrorContains(t, err, `unknown checks: "no-such"`)
+ err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
+ assert.ErrorContains(t, err, `unknown checks: "no-such"`)
+}
diff --git a/cmd/generate.go b/cmd/generate.go
index 5922617217a..90b32ecaf0e 100644
--- a/cmd/generate.go
+++ b/cmd/generate.go
@@ -18,7 +18,7 @@ var (
// CmdGenerate represents the available generate sub-command.
CmdGenerate = &cli.Command{
Name: "generate",
- Usage: "Command line interface for running generators",
+ Usage: "Generate Gitea's secrets/keys/tokens",
Subcommands: []*cli.Command{
subcmdSecret,
},
@@ -70,7 +70,7 @@ func runGenerateInternalToken(c *cli.Context) error {
}
func runGenerateLfsJwtSecret(c *cli.Context) error {
- _, jwtSecretBase64, err := generate.NewJwtSecretBase64()
+ _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
if err != nil {
return err
}
diff --git a/cmd/hook.go b/cmd/hook.go
index f38fd8831b8..6a3358853dd 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -31,8 +31,8 @@ var (
// CmdHook represents the available hooks sub-command.
CmdHook = &cli.Command{
Name: "hook",
- Usage: "Delegate commands to corresponding Git hooks",
- Description: "This should only be called by Git",
+ Usage: "(internal) Should only be called by Git",
+ Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Subcommands: []*cli.Command{
subcmdHookPreReceive,
@@ -376,7 +376,9 @@ Gitea or set your environment appropriately.`, "")
oldCommitIDs[count] = string(fields[0])
newCommitIDs[count] = string(fields[1])
refFullNames[count] = git.RefName(fields[2])
- if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
+
+ commitID, _ := git.NewIDFromString(newCommitIDs[count])
+ if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
masterPushed = true
}
count++
@@ -669,7 +671,8 @@ Gitea or set your environment appropriately.`, "")
if err != nil {
return err
}
- if rs.OldOID != git.EmptySHA {
+ commitID, _ := git.NewIDFromString(rs.OldOID)
+ if !commitID.IsZero() {
err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID))
if err != nil {
return err
diff --git a/cmd/keys.go b/cmd/keys.go
index b8467825293..7fdbe16119f 100644
--- a/cmd/keys.go
+++ b/cmd/keys.go
@@ -16,10 +16,11 @@ import (
// CmdKeys represents the available keys sub-command
var CmdKeys = &cli.Command{
- Name: "keys",
- Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint",
- Before: PrepareConsoleLoggerLevel(log.FATAL),
- Action: runKeys,
+ Name: "keys",
+ Usage: "(internal) Should only be called by SSH server",
+ Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
+ Before: PrepareConsoleLoggerLevel(log.FATAL),
+ Action: runKeys,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "expected",
@@ -70,13 +71,13 @@ func runKeys(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
- setup(ctx, false)
+ setup(ctx, c.Bool("debug"))
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
// do not use handleCliResponseExtra or cli.NewExitError, if it exists immediately, it breaks some tests like Test_CmdKeys
if extra.Error != nil {
return extra.Error
}
- _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString))
+ _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
return nil
}
diff --git a/cmd/mailer.go b/cmd/mailer.go
index 646330e85aa..0c5f2c8c8d4 100644
--- a/cmd/mailer.go
+++ b/cmd/mailer.go
@@ -45,6 +45,6 @@ func runSendMail(c *cli.Context) error {
if extra.HasError() {
return handleCliResponseExtra(extra)
}
- _, _ = fmt.Printf("Sent %s email(s) to all users\n", respText)
+ _, _ = fmt.Printf("Sent %s email(s) to all users\n", respText.Text)
return nil
}
diff --git a/cmd/main.go b/cmd/main.go
index feda41e68b2..02dd660e9ee 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -10,12 +10,12 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v2"
)
// cmdHelp is our own help subcommand with more information
+// Keep in mind that the "./gitea help"(subcommand) is different from "./gitea --help"(flag), the flag doesn't parse the config or output "DEFAULT CONFIGURATION:" information
func cmdHelp() *cli.Command {
c := &cli.Command{
Name: "help",
@@ -47,16 +47,10 @@ DEFAULT CONFIGURATION:
return c
}
-var helpFlag = cli.HelpFlag
-
-func init() {
- // cli.HelpFlag = nil TODO: after https://github.com/urfave/cli/issues/1794 we can use this
-}
-
func appGlobalFlags() []cli.Flag {
return []cli.Flag{
// make the builtin flags at the top
- helpFlag,
+ cli.HelpFlag,
// shared configuration flags, they are for global and for each sub-command at the same time
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
@@ -121,20 +115,22 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
func NewMainApp(version, versionExtra string) *cli.App {
app := cli.NewApp()
app.Name = "Gitea"
+ app.HelpName = "gitea"
app.Usage = "A painless self-hosted Git service"
- app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
+ app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
app.Version = version + versionExtra
app.EnableBashCompletion = true
// these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{
+ cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
CmdWeb,
CmdServ,
CmdHook,
+ CmdKeys,
CmdDump,
CmdAdmin,
CmdMigrate,
- CmdKeys,
CmdDoctor,
CmdManager,
CmdEmbedded,
@@ -142,13 +138,8 @@ func NewMainApp(version, versionExtra string) *cli.App {
CmdDumpRepository,
CmdRestoreRepository,
CmdActions,
- cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
}
- cmdConvert := util.ToPointer(*cmdDoctorConvert)
- cmdConvert.Hidden = true // still support the legacy "./gitea doctor" by the hidden sub-command, remove it in next release
- subCmdWithConfig = append(subCmdWithConfig, cmdConvert)
-
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
CmdCert,
diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go
index acc3ba16bab..aa49445a89e 100644
--- a/cmd/migrate_storage.go
+++ b/cmd/migrate_storage.go
@@ -110,6 +110,9 @@ func migrateLFS(ctx context.Context, dstStorage storage.ObjectStorage) error {
func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error {
+ if user.CustomAvatarRelativePath() == "" {
+ return nil
+ }
_, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath())
return err
})
@@ -117,6 +120,9 @@ func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error
func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error {
+ if repo.CustomAvatarRelativePath() == "" {
+ return nil
+ }
_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
return err
})
diff --git a/cmd/serv.go b/cmd/serv.go
index 26fc91a3b7a..90190a19db6 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -42,7 +42,7 @@ const (
// CmdServ represents the available serv sub-command.
var CmdServ = &cli.Command{
Name: "serv",
- Usage: "This command should only be called by SSH shell",
+ Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ,
@@ -63,21 +63,10 @@ func setup(ctx context.Context, debug bool) {
setupConsoleLogger(log.FATAL, false, os.Stderr)
}
setting.MustInstalled()
- if debug {
- setting.RunMode = "dev"
- }
-
- // Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
- // `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
if _, err := os.Stat(setting.RepoRootPath); err != nil {
- if os.IsNotExist(err) {
- _ = fail(ctx, "Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath)
- } else {
- _ = fail(ctx, "Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err)
- }
+ _ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
return
}
-
if err := git.InitSimple(context.Background()); err != nil {
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
}
@@ -216,16 +205,18 @@ func runServ(c *cli.Context) error {
}
}
- // LowerCase and trim the repoPath as that's how they are stored.
- repoPath = strings.ToLower(strings.TrimSpace(repoPath))
-
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
}
- username := strings.ToLower(rr[0])
- reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
+ username := rr[0]
+ reponame := strings.TrimSuffix(rr[1], ".git")
+
+ // LowerCase and trim the repoPath as that's how they are stored.
+ // This should be done after splitting the repoPath into username and reponame
+ // so that username and reponame are not affected.
+ repoPath = strings.ToLower(strings.TrimSpace(repoPath))
if alphaDashDotPattern.MatchString(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go
index 5cd0fe0f6e6..820c0702b74 100644
--- a/contrib/backport/backport.go
+++ b/contrib/backport/backport.go
@@ -17,7 +17,7 @@ import (
"strings"
"syscall"
- "github.com/google/go-github/v53/github"
+ "github.com/google/go-github/v57/github"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
)
diff --git a/contrib/systemd/gitea.service b/contrib/systemd/gitea.service
index c097fb0d176..d205c6ee8ba 100644
--- a/contrib/systemd/gitea.service
+++ b/contrib/systemd/gitea.service
@@ -52,7 +52,7 @@ After=network.target
# Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that
# LimitNOFILE=524288:524288
RestartSec=2s
-Type=notify
+Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
@@ -62,7 +62,6 @@ WorkingDirectory=/var/lib/gitea/
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
-WatchdogSec=30s
# If you install Git to directory prefix other than default PATH (which happens
# for example if you install other versions of Git side-to-side with
# distribution version), uncomment below line and add that prefix to PATH
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index cf1895c348c..423feb0cacc 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -234,7 +234,7 @@ RUN_USER = ; git
;MINIMUM_KEY_SIZE_CHECK = false
;;
;; Disable CDN even in "prod" mode
-;OFFLINE_MODE = false
+;OFFLINE_MODE = true
;;
;; TLS Settings: Either ACME or manual
;; (Other common TLS configuration are found before)
@@ -351,6 +351,7 @@ NAME = gitea
USER = root
;PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password.
;SSL_MODE = false ; either "false" (default), "true", or "skip-verify"
+;CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
@@ -382,6 +383,7 @@ USER = root
;NAME = gitea
;USER = SA
;PASSWD = MwantsaSecurePassword1
+;CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
@@ -410,6 +412,10 @@ USER = root
;;
;; Whether execute database models migrations automatically
;AUTO_MIGRATION = true
+;;
+;; Threshold value (in seconds) beyond which query execution time is logged as a warning in the xorm logger
+;;
+;SLOW_QUERY_THRESHOLD = 5s
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -429,7 +435,7 @@ SECRET_KEY =
;SECRET_KEY_URI = file:/etc/gitea/secret_key
;;
;; Secret used to validate communication within Gitea binary.
-INTERNAL_TOKEN=
+INTERNAL_TOKEN =
;;
;; Alternative location to specify internal token, instead of this file; you cannot specify both this and INTERNAL_TOKEN, and must pick one
;INTERNAL_TOKEN_URI = file:/etc/gitea/internal_token
@@ -492,6 +498,11 @@ INTERNAL_TOKEN=
;; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations.
;; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
;SUCCESSFUL_TOKENS_CACHE_SIZE = 20
+;;
+;; Reject API tokens sent in URL query string (Accept Header-based API tokens only). This avoids security vulnerabilities
+;; stemming from cached/logged plain-text API tokens.
+;; In future releases, this will become the default behavior
+;DISABLE_QUERY_AUTH_TOKEN = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -517,7 +528,7 @@ INTERNAL_TOKEN=
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Enables OAuth2 provider
-ENABLE = true
+ENABLED = true
;;
;; Algorithm used to sign OAuth2 tokens. Valid values: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, EdDSA
;JWT_SIGNING_ALGORITHM = RS256
@@ -1046,7 +1057,7 @@ LEVEL = Info
;; List of keywords used in Pull Request comments to automatically reopen a related issue
;REOPEN_KEYWORDS = reopen,reopens,reopened
;;
-;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash
+;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash, fast-forward-only
;DEFAULT_MERGE_STYLE = merge
;;
;; In the default merge message for squash commits include at most this many commits
@@ -1069,6 +1080,9 @@ LEVEL = Info
;;
;; In addition to testing patches using the three-way merge method, re-test conflicting patches with git apply
;TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY = false
+;;
+;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo.
+;RETARGET_CHILDREN_ON_MERGE = true
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1162,15 +1176,9 @@ LEVEL = Info
;; enable cors headers (disabled by default)
;ENABLED = false
;;
-;; scheme of allowed requests
-;SCHEME = http
-;;
-;; list of requesting domains that are allowed
+;; list of requesting origins that are allowed, eg: "https://*.example.com"
;ALLOW_DOMAIN = *
;;
-;; allow subdomains of headers listed above to request
-;ALLOW_SUBDOMAIN = false
-;;
;; list of methods allowed to request
;METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
;;
@@ -1216,6 +1224,9 @@ LEVEL = Info
;; Max size of files to be displayed (default is 8MiB)
;MAX_DISPLAY_FILE_SIZE = 8388608
;;
+;; Detect ambiguous unicode characters in file contents and show warnings on the UI
+;AMBIGUOUS_UNICODE_DETECTION = true
+;;
;; Whether the email of the user should be shown in the Explore Users page
;SHOW_USER_EMAIL = true
;;
@@ -1251,6 +1262,10 @@ LEVEL = Info
;; Change the sort type of the explore pages.
;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest".
;EXPLORE_PAGING_DEFAULT_SORT = recentupdate
+;;
+;; The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`.
+;; `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago).
+;PREFERRED_TIMESTAMP_TENSE = mixed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1468,6 +1483,9 @@ LEVEL = Info
;;
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
+;; Disabled features for users, could be "deletion", more features can be disabled in future
+;; - deletion: a user cannot delete their own account
+;USER_DISABLED_FEATURES =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1532,6 +1550,10 @@ LEVEL = Info
;; userid = use the userid / sub attribute
;; nickname = use the nickname attribute
;; email = use the username part of the email attribute
+;; Note: `nickname` and `email` options will normalize input strings using the following criteria:
+;; - diacritics are removed
+;; - the characters in the set `['´\x60]` are removed
+;; - the characters in the set `[\s~+]` are replaced with `-`
;USERNAME = nickname
;;
;; Update avatar if available from oauth2 provider.
@@ -1706,9 +1728,6 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
-;; if the cache enabled
-;ENABLED = true
-;;
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
;ADAPTER = memory
;;
@@ -1733,8 +1752,6 @@ LEVEL = Info
;[cache.last_commit]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; if the cache enabled
-;ENABLED = true
;;
;; Time to keep items in cache if not used, default is 8760 hours.
;; Setting it to -1 disables caching
@@ -2584,7 +2601,7 @@ LEVEL = Info
;;
;; 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
+;; Default artifact retention time in days. Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
;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
diff --git a/docs/content/administration/adding-legal-pages.en-us.md b/docs/content/administration/adding-legal-pages.en-us.md
index c6f68edcd0e..1ff0c0132d2 100644
--- a/docs/content/administration/adding-legal-pages.en-us.md
+++ b/docs/content/administration/adding-legal-pages.en-us.md
@@ -19,10 +19,10 @@ Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Poli
## Getting Pages
-Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/`. For example, to add Privacy Policy:
+Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/assets/`. For example, to add Privacy Policy:
```
-wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
+wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
```
Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation.
diff --git a/docs/content/administration/adding-legal-pages.zh-cn.md b/docs/content/administration/adding-legal-pages.zh-cn.md
index 5d582e871de..3e18c6e6b03 100644
--- a/docs/content/administration/adding-legal-pages.zh-cn.md
+++ b/docs/content/administration/adding-legal-pages.zh-cn.md
@@ -19,10 +19,10 @@ menu:
## 获取页面
-Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/` 目录下。例如,如果要添加隐私政策:
+Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/assets/` 目录下。例如,如果要添加隐私政策:
```
-wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
+wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
```
现在,你需要编辑该页面以满足你的需求。特别是,你必须更改电子邮件地址、网址以及与 "Your Gitea Instance" 相关的引用,以匹配你的情况。
diff --git a/docs/content/administration/backup-and-restore.en-us.md b/docs/content/administration/backup-and-restore.en-us.md
index d46efecf990..451ef5c944c 100644
--- a/docs/content/administration/backup-and-restore.en-us.md
+++ b/docs/content/administration/backup-and-restore.en-us.md
@@ -92,7 +92,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
-mv repos/* /var/lib/gitea/gitea-repositories/
+mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -111,6 +111,8 @@ With Gitea running, and from the directory Gitea's binary is located, execute: `
This ensures that application and configuration file paths in repository Git Hooks are consistent and applicable to the current installation. If these paths are not updated, repository `push` actions will fail.
+If you still have issues, consider running `./gitea doctor check` to inspect possible errors (or run with `--fix`).
+
### Using Docker (`restore`)
There is also no support for a recovery command in a Docker-based gitea instance. The restore process contains the same steps as described in the previous section but with different paths.
diff --git a/docs/content/administration/backup-and-restore.zh-cn.md b/docs/content/administration/backup-and-restore.zh-cn.md
index 98d378d5dcb..db7eba84f7e 100644
--- a/docs/content/administration/backup-and-restore.zh-cn.md
+++ b/docs/content/administration/backup-and-restore.zh-cn.md
@@ -19,6 +19,12 @@ menu:
Gitea 已经实现了 `dump` 命令可以用来备份所有需要的文件到一个zip压缩文件。该压缩文件可以被用来进行数据恢复。
+## 备份一致性
+
+为了确保 Gitea 实例的一致性,在备份期间必须关闭它。
+
+Gitea 包括数据库、文件和 Git 仓库,当它被使用时所有这些都会发生变化。例如,当迁移正在进行时,在数据库中创建一个事务,而 Git 仓库正在被复制。如果备份发生在迁移的中间,Git 仓库可能是不完整的,尽管数据库声称它是完整的,因为它是在之后被转储的。避免这种竞争条件的唯一方法是在备份期间停止 Gitea 实例。
+
## 备份命令 (`dump`)
先转到git用户的权限: `su git`. 再Gitea目录运行 `./gitea dump`。一般会显示类似如下的输出:
@@ -34,15 +40,43 @@ Gitea 已经实现了 `dump` 命令可以用来备份所有需要的文件到一
最后生成的 `gitea-dump-1482906742.zip` 文件将会包含如下内容:
-* `custom` - 所有保存在 `custom/` 目录下的配置和自定义的文件。
-* `data` - 数据目录下的所有内容不包含使用文件session的文件。该目录包含 `attachments`, `avatars`, `lfs`, `indexers`, 如果使用sqlite 还会包含 sqlite 数据库文件。
+* `app.ini` - 如果原先存储在默认的 custom/ 目录之外,则是配置文件的可选副本
+* `custom/` - 所有保存在 `custom/` 目录下的配置和自定义的文件。
+* `data/` - 数据目录(APP_DATA_PATH),如果使用文件会话,则不包括会话。该目录包括 `attachments`、`avatars`、`lfs`、`indexers`、如果使用 SQLite 则包括 SQLite 文件。
+* `repos/` - 仓库目录的完整副本。
* `gitea-db.sql` - 数据库dump出来的 SQL。
-* `gitea-repo.zip` - Git仓库压缩文件。
* `log/` - Logs文件,如果用作迁移不是必须的。
中间备份文件将会在临时目录进行创建,如果您要重新指定临时目录,可以用 `--tempdir` 参数,或者用 `TMPDIR` 环境变量。
-## Restore Command (`restore`)
+## 备份数据库
+
+`gitea dump` 创建的 SQL 转储使用 XORM,Gitea 管理员可能更喜欢使用本地的 MySQL 和 PostgreSQL 转储工具。使用 XORM 转储数据库时仍然存在一些问题,可能会导致在尝试恢复时出现问题。
+
+```sh
+# mysql
+mysqldump -u$USER -p$PASS --database $DATABASE > gitea-db.sql
+# postgres
+pg_dump -U $USER $DATABASE > gitea-db.sql
+```
+
+### 使用Docker (`dump`)
+
+在使用 Docker 时,使用 `dump` 命令有一些注意事项。
+
+必须以 `gitea/conf/app.ini` 中指定的 `RUN_USER = ` 执行该命令;并且,为了让备份文件夹的压缩过程能够顺利执行,`docker exec` 命令必须在 `--tempdir` 内部执行。
+
+示例:
+
+```none
+docker exec -u -it -w <--tempdir> $(docker ps -qf 'name=^$') bash -c '/usr/local/bin/gitea dump -c '
+```
+
+\*注意:`--tempdir` 指的是 Gitea 使用的 Docker 环境的临时目录;如果您没有指定自定义的 `--tempdir`,那么 Gitea 将使用 `/tmp` 或 Docker 容器的 `TMPDIR` 环境变量。对于 `--tempdir`,请相应调整您的 `docker exec` 命令选项。
+
+结果应该是一个文件,存储在指定的 `--tempdir` 中,类似于:`gitea-dump-1482906742.zip`
+
+## 恢复命令 (`restore`)
当前还没有恢复命令,恢复需要人工进行。主要是把文件和数据库进行恢复。
@@ -51,10 +85,10 @@ Gitea 已经实现了 `dump` 命令可以用来备份所有需要的文件到一
```sh
unzip gitea-dump-1610949662.zip
cd gitea-dump-1610949662
-mv data/conf/app.ini /etc/gitea/conf/app.ini
+mv 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/
+mv repos/* /var/lib/gitea/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -66,3 +100,55 @@ psql -U $USER -d $DATABASE < gitea-db.sql
service gitea restart
```
+
+如果安装方式发生了变化(例如 二进制 -> Docker),或者 Gitea 安装到了与之前安装不同的目录,则需要重新生成仓库 Git 钩子。
+
+在 Gitea 运行时,并从 Gitea 二进制文件所在的目录执行:`./gitea admin regenerate hooks`
+
+这样可以确保仓库 Git 钩子中的应用程序和配置文件路径与当前安装一致。如果这些路径没有更新,仓库的 `push` 操作将失败。
+
+### 使用 Docker (`restore`)
+
+在基于 Docker 的 Gitea 实例中,也没有恢复命令的支持。恢复过程与前面描述的步骤相同,但路径不同。
+
+示例:
+
+```sh
+# 在容器中打开 bash 会话
+docker exec --user git -it 2a83b293548e bash
+# 在容器内解压您的备份文件
+unzip gitea-dump-1610949662.zip
+cd gitea-dump-1610949662
+# 恢复 Gitea 数据
+mv data/* /data/gitea
+# 恢复仓库本身
+mv repos/* /data/git/gitea-repositories/
+# 调整文件权限
+chown -R git:git /data
+# 重新生成 Git 钩子
+/usr/local/bin/gitea -c '/data/gitea/conf/app.ini' admin regenerate hooks
+```
+
+Gitea 容器中的默认用户是 `git`(1000:1000)。请用您的 Gitea 容器 ID 或名称替换 `2a83b293548e`。
+
+### 使用 Docker-rootless (`restore`)
+
+在 Docker-rootless 容器中的恢复工作流程只是要使用的目录不同:
+
+```sh
+# 在容器中打开 bash 会话
+docker exec --user git -it 2a83b293548e bash
+# 在容器内解压您的备份文件
+unzip gitea-dump-1610949662.zip
+cd gitea-dump-1610949662
+# 恢复 app.ini
+mv data/conf/app.ini /etc/gitea/app.ini
+# 恢复 Gitea 数据
+mv data/* /var/lib/gitea
+# 恢复仓库本身
+mv repos/* /var/lib/gitea/git/gitea-repositories
+# 调整文件权限
+chown -R git:git /etc/gitea/app.ini /var/lib/gitea
+# 重新生成 Git 钩子
+/usr/local/bin/gitea -c '/etc/gitea/app.ini' admin regenerate hooks
+```
diff --git a/docs/content/administration/command-line.en-us.md b/docs/content/administration/command-line.en-us.md
index a52b93d344c..5049df35e05 100644
--- a/docs/content/administration/command-line.en-us.md
+++ b/docs/content/administration/command-line.en-us.md
@@ -95,6 +95,7 @@ Admin operations:
- Options:
- `--username value`, `-u value`: Username. Required.
- `--password value`, `-p value`: New password. Required.
+ - `--must-change-password`: If provided, the user is required to choose a new password after the login. Optional.
- Examples:
- `gitea admin user change-password --username myname --password asecurepassword`
- `must-change-password`:
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index 967890b4348..81509d7d4da 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -129,7 +129,7 @@ limit defined in `REPO_SIZE_LIMIT` or at the repository itself.
keywords used in Pull Request comments to automatically close a related issue
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen
a related issue
-- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash`
+- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only`
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: In the default merge message for squash commits include at most this many commits. Set to `-1` to include all commits
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: In the default merge message for squash commits limit the size of the commit messages. Set to `-1` to have no limit. Only used if `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES` is `true`.
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list
@@ -138,6 +138,7 @@ limit defined in `REPO_SIZE_LIMIT` or at the repository itself.
- `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`: **false**: In default squash-merge messages include the commit message of all commits comprising the pull request.
- `ADD_CO_COMMITTER_TRAILERS`: **true**: Add co-authored-by and co-committed-by trailers to merge commit messages if committer does not match author.
- `TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY`: **false**: PR patches are tested using a three-way merge method to discover if there are conflicts. If this setting is set to **true**, conflicting patches will be retested using `git apply` - This was the previous behaviour in 1.18 (and earlier) but is somewhat inefficient. Please report if you find that this setting is required.
+- `RETARGET_CHILDREN_ON_MERGE`: **true**: Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo.
### Repository - Issue (`repository.issue`)
@@ -199,9 +200,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
## CORS (`cors`)
- `ENABLED`: **false**: enable cors headers (disabled by default)
-- `SCHEME`: **http**: scheme of allowed requests
-- `ALLOW_DOMAIN`: **\***: list of requesting domains that are allowed
-- `ALLOW_SUBDOMAIN`: **false**: allow subdomains of headers listed above to request
+- `ALLOW_DOMAIN`: **\***: list of requesting origins that are allowed, eg: "https://*.example.com"
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: list of methods allowed to request
- `MAX_AGE`: **10m**: max time to cache response
- `ALLOW_CREDENTIALS`: **false**: allow request with credentials
@@ -223,6 +222,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: All available themes. Allow users select personalized themes.
regardless of the value of `DEFAULT_THEME`.
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
+- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
Values can be emoji alias (:smile:) or a unicode emoji.
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
@@ -235,6 +235,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).
- `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest"
+- `PREFERRED_TIMESTAMP_TENSE`: **mixed**: The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`. `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago).
### UI - Admin (`ui.admin`)
@@ -359,7 +360,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `SSH_PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to SSH connections.
- `MINIMUM_KEY_SIZE_CHECK`: **true**: Indicate whether to check minimum key size with corresponding type.
-- `OFFLINE_MODE`: **false**: Disables use of CDN for static files and Gravatar for profile pictures.
+- `OFFLINE_MODE`: **true**: Disables use of CDN for static files and Gravatar for profile pictures.
- `CERT_FILE`: **https/cert.pem**: Cert file path used for HTTPS. When chaining, the server certificate must come first, then intermediate CA certificates (if any). This is ignored if `ENABLE_ACME=true`. Paths are relative to `CUSTOM_PATH`.
- `KEY_FILE`: **https/key.pem**: Key file path used for HTTPS. This is ignored if `ENABLE_ACME=true`. Paths are relative to `CUSTOM_PATH`.
- `STATIC_ROOT_PATH`: **_`StaticRootPath`_**: Upper level of template and static files path.
@@ -433,6 +434,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `NAME`: **gitea**: Database name.
- `USER`: **root**: Database username.
- `PASSWD`: **_empty_**: Database user password. Use \`your password\` or """your password""" for quoting if you use special characters in the password.
+- `CHARSET_COLLATION`: **_empty_**: (MySQL/MSSQL only) Gitea expects to use a case-sensitive collation for database. Leave it empty to use the default collation decided by the Gitea. Don't change it unless you clearly know what you need.
- `SCHEMA`: **_empty_**: For PostgreSQL only, schema to use if different from "public". The schema must exist beforehand,
the user must have creation privileges on it, and the user search path must be set to the look into the schema first
(e.g. `ALTER USER user SET SEARCH_PATH = schema_name,"$user",public;`).
@@ -459,6 +461,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `MAX_IDLE_CONNS` **2**: Max idle database connections on connection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`.
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
+- `SLOW_QUERY_THRESHOLD` **5s**: Threshold value in seconds beyond which query execution time is logged as a warning in the xorm logger.
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
@@ -518,6 +521,8 @@ And the following unique queues:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
+- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion` and more features can be added in future.
+ - `deletion`: User cannot delete their own account.
## Security (`security`)
@@ -575,6 +580,7 @@ And the following unique queues:
- off - do not check password complexity
- `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed.
- `SUCCESSFUL_TOKENS_CACHE_SIZE`: **20**: Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
+- `DISABLE_QUERY_AUTH_TOKEN`: **false**: Reject API tokens sent in URL query string (Accept Header-based API tokens only). This setting will default to `true` in Gitea 1.23 and be deprecated in Gitea 1.24.
## Camo (`camo`)
@@ -598,9 +604,13 @@ And the following unique queues:
- `OPENID_CONNECT_SCOPES`: **_empty_**: List of additional openid connect scopes. (`openid` is implicitly added)
- `ENABLE_AUTO_REGISTRATION`: **false**: Automatically create user accounts for new oauth2 users.
- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts:
- - userid - use the userid / sub attribute
- - nickname - use the nickname attribute
- - email - use the username part of the email attribute
+ - `userid` - use the userid / sub attribute
+ - `nickname` - use the nickname attribute
+ - `email` - use the username part of the email attribute
+ - Note: `nickname` and `email` options will normalize input strings using the following criteria:
+ - diacritics are removed
+ - the characters in the set `['´\x60]` are removed
+ - the characters in the set `[\s~+]` are replaced with `-`
- `UPDATE_AVATAR`: **false**: Update avatar if available from oauth2 provider. Update will be performed on each login.
- `ACCOUNT_LINKING`: **login**: How to handle if an account / email already exists:
- disabled - show an error
@@ -764,7 +774,6 @@ and
## Cache (`cache`)
-- `ENABLED`: **true**: Enable the cache.
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, `redis-cluster`, `twoqueue` or `memcache`. (`twoqueue` represents a size limited LRU cache.)
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory and twoqueue cache only.
- `HOST`: **_empty_**: Connection string for `redis`, `redis-cluster` and `memcache`. For `twoqueue` sets configuration for the queue.
@@ -776,7 +785,6 @@ and
## Cache - LastCommitCache settings (`cache.last_commit`)
-- `ENABLED`: **true**: Enable the cache.
- `ITEM_TTL`: **8760h**: Time to keep items in cache if not used, Setting it to -1 disables caching.
- `COMMITS_COUNT`: **1000**: Only enable the cache when repository's commits count great than.
@@ -1105,7 +1113,7 @@ This section only does "set" config, a removed config key from this section won'
## OAuth2 (`oauth2`)
-- `ENABLE`: **true**: Enables OAuth2 provider.
+- `ENABLED`: **true**: Enables OAuth2 provider.
- `ACCESS_TOKEN_EXPIRATION_TIME`: **3600**: Lifetime of an OAuth2 access token in seconds
- `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 refresh token in hours
- `INVALIDATE_REFRESH_TOKENS`: **false**: Check if refresh token has already been used
@@ -1395,15 +1403,15 @@ PROXY_HOSTS = *.github.com
- `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.
+- `ARTIFACT_RETENTION_DAYS`: **90**: Default number of days to keep artifacts. Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
- `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
- `SKIP_WORKFLOW_STRINGS`: **[skip ci],[ci skip],[no ci],[skip actions],[actions skip]**: Strings committers can place inside a commit message to skip executing the corresponding actions workflow
`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`.
-And it can be changed to `self` to make it `root_url_of_your_gitea/actions/checkout@v3`.
+For example, `uses: actions/checkout@v4` means `https://github.com/actions/checkout@v4` since the value of `DEFAULT_ACTIONS_URL` is `github`.
+And it can be changed to `self` to make it `root_url_of_your_gitea/actions/checkout@v4`.
Please note that using `self` is not recommended for most cases, as it could make names globally ambiguous.
Additionally, it requires you to mirror all the actions you need to your Gitea instance, which may not be worth it.
@@ -1412,7 +1420,7 @@ Therefore, please use `self` only if you understand what you are doing.
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`.
+Like `uses: https://gitea.com/actions/checkout@v4` or `uses: http://your-git-server/actions/checkout@v4`.
## Other (`other`)
diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md
index c4e3876c1fe..5fe0a62215f 100644
--- a/docs/content/administration/config-cheat-sheet.zh-cn.md
+++ b/docs/content/administration/config-cheat-sheet.zh-cn.md
@@ -29,7 +29,7 @@ menu:
[ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。
标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。
-在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`enviroment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。
+在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`environment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。
包含`#`或者`;`的变量必须使用引号(`` ` ``或者`""""`)包裹,否则会被解析为注释。
@@ -125,7 +125,7 @@ menu:
- `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: 在拉取请求评论中用于自动重新打开相关问题的
关键词列表。
-- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash`
+- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only`
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: 在默认合并消息中,对于`squash`提交,最多包括此数量的提交。设置为 -1 以包括所有提交。
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: 在默认合并消息中,对于`squash`提交,遍历所有提交以包括所有作者的`Co-authored-by`,否则仅使用限定列表中的作者。
@@ -195,9 +195,7 @@ menu:
## 跨域 (`cors`)
- `ENABLED`: **false**: 启用 CORS 头部(默认禁用)
-- `SCHEME`: **http**: 允许请求的协议
- `ALLOW_DOMAIN`: **\***: 允许请求的域名列表
-- `ALLOW_SUBDOMAIN`: **false**: 允许上述列出的头部的子域名发出请求。
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: 允许发起的请求方式列表
- `MAX_AGE`: **10m**: 缓存响应的最大时间
- `ALLOW_CREDENTIALS`: **false**: 允许带有凭据的请求
@@ -346,7 +344,7 @@ menu:
- `SSH_PER_WRITE_TIMEOUT`: **30s**:对 SSH 连接的任何写入设置超时。(将其设置为 -1 可以禁用所有超时。)
- `SSH_PER_WRITE_PER_KB_TIMEOUT`: **10s**:对写入 SSH 连接的每 KB 设置超时。
- `MINIMUM_KEY_SIZE_CHECK`: **true**:指示是否检查最小密钥大小与相应类型。
-- `OFFLINE_MODE`: **false**:禁用 CDN 用于静态文件和 Gravatar 用于个人资料图片。
+- `OFFLINE_MODE`: **true**:禁用 CDN 用于静态文件和 Gravatar 用于个人资料图片。
- `CERT_FILE`: **https/cert.pem**:用于 HTTPS 的证书文件路径。在链接时,服务器证书必须首先出现,然后是中间 CA 证书(如果有)。如果 `ENABLE_ACME=true`,则此设置会被忽略。路径相对于 `CUSTOM_PATH`。
- `KEY_FILE`: **https/key.pem**:用于 HTTPS 的密钥文件路径。如果 `ENABLE_ACME=true`,则此设置会被忽略。路径相对于 `CUSTOM_PATH`。
- `STATIC_ROOT_PATH`: **_`StaticRootPath`_**:模板和静态文件路径的上一级。
@@ -499,6 +497,8 @@ Gitea 创建以下非唯一队列:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
+- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`, 未来可以增加更多设置。
+ - `deletion`: 用户不能通过界面或者API删除他自己。
## 安全性 (`security`)
@@ -721,7 +721,6 @@ Gitea 创建以下非唯一队列:
## 缓存 (`cache`)
-- `ENABLED`: **true**: 是否启用缓存。
- `ADAPTER`: **memory**: 缓存引擎,可以为 `memory`, `redis`, `redis-cluster`, `twoqueue` 和 `memcache`. (`twoqueue` 代表缓冲区固定的LRU缓存)
- `INTERVAL`: **60**: 垃圾回收间隔(秒),只对`memory`和`towqueue`有效。
- `HOST`: **_empty_**: 缓存配置。`redis`, `redis-cluster`,`memcache`配置连接字符串;`twoqueue` 设置队列参数
@@ -733,7 +732,6 @@ Gitea 创建以下非唯一队列:
### 缓存 - 最后提交缓存设置 (`cache.last_commit`)
-- `ENABLED`: **true**:是否启用缓存。
- `ITEM_TTL`: **8760h**:如果未使用,保持缓存中的项目的时间,将其设置为 -1 会禁用缓存。
- `COMMITS_COUNT`: **1000**:仅在存储库的提交计数大于时启用缓存。
@@ -991,7 +989,7 @@ Gitea 创建以下非唯一队列:
- `LAST_UPDATED_MORE_THAN_AGO`: **72h**: 只会尝试回收超过此时间(默认3天)没有尝试过回收的 LFSMetaObject。
- `NUMBER_TO_CHECK_PER_REPO`: **100**: 每个仓库要检查的过期 LFSMetaObject 的最小数量。设置为 `0` 以始终检查所有。
-# Git (`git`)
+## Git (`git`)
- `PATH`: **""**: Git可执行文件的路径。如果为空,Gitea将在PATH环境中搜索。
- `HOME_PATH`: **%(APP_DATA_PATH)s/home**: Git的HOME目录。
@@ -1039,14 +1037,15 @@ Gitea 创建以下非唯一队列:
## API (`api`)
-- `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 (`/api/swagger`, `/api/v1/swagger`, …)。
-- `MAX_RESPONSE_ITEMS`: **50**: 单个页面的最大 Feed.
-- `ENABLE_OPENID_SIGNIN`: **false**: 允许使用OpenID登录,当设置为`true`时可以通过 `/user/login` 页面进行OpenID登录。
-- `DISABLE_REGISTRATION`: **false**: 关闭用户注册。
+- `ENABLE_SWAGGER`: **true**: 启用API文档接口 (`/api/swagger`, `/api/v1/swagger`, …). True or false。
+- `MAX_RESPONSE_ITEMS`: **50**: API分页的最大单页项目数。
+- `DEFAULT_PAGING_NUM`: **30**: API分页的默认分页数。
+- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Git trees API的默认单页项目数。
+- `DEFAULT_MAX_BLOB_SIZE`: **10485760** (10MiB): blobs API的默认最大文件大小。
## OAuth2 (`oauth2`)
-- `ENABLE`: **true**:启用OAuth2提供者。
+- `ENABLED`: **true**:启用OAuth2提供者。
- `ACCESS_TOKEN_EXPIRATION_TIME`:**3600**:OAuth2访问令牌的生命周期,以秒为单位。
- `REFRESH_TOKEN_EXPIRATION_TIME`:**730**:OAuth2刷新令牌的生命周期,以小时为单位。
- `INVALIDATE_REFRESH_TOKENS`:**false**:检查刷新令牌是否已被使用。
@@ -1336,8 +1335,8 @@ PROXY_HOSTS = *.github.com
- `MINIO_BASE_PATH`: **actions_log/**:Minio存储桶上的基本路径,仅在`STORAGE_TYPE`为`minio`时可用。
`DEFAULT_ACTIONS_URL` 指示 Gitea 操作运行程序应该在哪里找到带有相对路径的操作。
-例如,`uses: actions/checkout@v3` 表示 `https://github.com/actions/checkout@v3`,因为 `DEFAULT_ACTIONS_URL` 的值为 `github`。
-它可以更改为 `self`,以使其成为 `root_url_of_your_gitea/actions/checkout@v3`。
+例如,`uses: actions/checkout@v4` 表示 `https://github.com/actions/checkout@v4`,因为 `DEFAULT_ACTIONS_URL` 的值为 `github`。
+它可以更改为 `self`,以使其成为 `root_url_of_your_gitea/actions/checkout@v4`。
请注意,对于大多数情况,不建议使用 `self`,因为它可能使名称在全局范围内产生歧义。
此外,它要求您将所有所需的操作镜像到您的 Gitea 实例,这可能不值得。
@@ -1346,7 +1345,7 @@ PROXY_HOSTS = *.github.com
在早期版本(`<= 1.19`)中,`DEFAULT_ACTIONS_URL` 可以设置为任何自定义 URL,例如 `https://gitea.com` 或 `http://your-git-server,https://gitea.com`,默认值为 `https://gitea.com`。
然而,后来的更新删除了这些选项,现在唯一的选项是 `github` 和 `self`,默认值为 `github`。
但是,如果您想要使用其他 Git 服务器中的操作,您可以在 `uses` 字段中使用完整的 URL,Gitea 支持此功能(GitHub 不支持)。
-例如 `uses: https://gitea.com/actions/checkout@v3` 或 `uses: http://your-git-server/actions/checkout@v3`。
+例如 `uses: https://gitea.com/actions/checkout@v4` 或 `uses: http://your-git-server/actions/checkout@v4`。
## 其他 (`other`)
diff --git a/docs/content/administration/customizing-gitea.en-us.md b/docs/content/administration/customizing-gitea.en-us.md
index d122fb4bfaa..7efddb2824e 100644
--- a/docs/content/administration/customizing-gitea.en-us.md
+++ b/docs/content/administration/customizing-gitea.en-us.md
@@ -284,7 +284,7 @@ syntax and shouldn't be touched without fully understanding these components.
Google Analytics, Matomo (previously Piwik), and other analytics services can be added to Gitea. To add the tracking code, refer to the `Other additions to the page` section of this document, and add the JavaScript to the `$GITEA_CUSTOM/templates/custom/header.tmpl` file.
-## Customizing gitignores, labels, licenses, locales, and readmes.
+## Customizing gitignores, labels, licenses, locales, and readmes
Place custom files in corresponding sub-folder under `custom/options`.
diff --git a/docs/content/administration/customizing-gitea.zh-cn.md b/docs/content/administration/customizing-gitea.zh-cn.md
index d828704557c..f41533c69cb 100644
--- a/docs/content/administration/customizing-gitea.zh-cn.md
+++ b/docs/content/administration/customizing-gitea.zh-cn.md
@@ -42,11 +42,11 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
将自定义的公共文件(比如页面和图片)作为 webroot 放在 `custom/public/` 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。
-举例说明:`image.png` 存放在 `custom/public/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
+举例说明:`image.png` 存放在 `custom/public/assets/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
## 修改默认头像
-替换以下目录中的 png 图片: `custom/public/img/avatar\_default.png`
+替换以下目录中的 png 图片: `custom/public/assets/img/avatar\_default.png`
## 自定义 Gitea 页面
diff --git a/docs/content/administration/environment-variables.en-us.md b/docs/content/administration/environment-variables.en-us.md
index f910cf060e8..2c6fcbe681d 100644
--- a/docs/content/administration/environment-variables.en-us.md
+++ b/docs/content/administration/environment-variables.en-us.md
@@ -27,14 +27,15 @@ GITEA_CUSTOM=/home/gitea/custom ./gitea web
## From Go language
-As Gitea is written in Go, it uses some Go variables, such as:
+As Gitea is written in Go, it uses some variables that influence the behaviour of Go's runtime, such as:
-- `GOOS`
-- `GOARCH`
-- [`GOPATH`](https://golang.org/cmd/go/#hdr-GOPATH_environment_variable)
+- `GOMEMLIMIT`
+- `GOGC`
+- `GOMAXPROCS`
+- `GODEBUG`
For documentation about each of the variables available, refer to the
-[official Go documentation](https://golang.org/cmd/go/#hdr-Environment_variables).
+[official Go documentation on runtime environment variables](https://pkg.go.dev/runtime#hdr-Environment_Variables).
## Gitea files
diff --git a/docs/content/administration/environment-variables.zh-cn.md b/docs/content/administration/environment-variables.zh-cn.md
index 25e120becdc..d5be9e03c25 100644
--- a/docs/content/administration/environment-variables.zh-cn.md
+++ b/docs/content/administration/environment-variables.zh-cn.md
@@ -29,9 +29,9 @@ GITEA_CUSTOM=/home/gitea/custom ./gitea web
* `GOOS`
* `GOARCH`
-* [`GOPATH`](https://golang.org/cmd/go/#hdr-GOPATH_environment_variable)
+* [`GOPATH`](https://go.dev/cmd/go/#hdr-GOPATH_environment_variable)
-您可以在[官方文档](https://golang.org/cmd/go/#hdr-Environment_variables)中查阅这些配置参数的详细信息。
+您可以在[官方文档](https://go.dev/cmd/go/#hdr-Environment_variables)中查阅这些配置参数的详细信息。
## Gitea 的文件目录
diff --git a/docs/content/administration/external-renderers.zh-cn.md b/docs/content/administration/external-renderers.zh-cn.md
index 0b53b452776..fdf7315d7b7 100644
--- a/docs/content/administration/external-renderers.zh-cn.md
+++ b/docs/content/administration/external-renderers.zh-cn.md
@@ -194,7 +194,7 @@ ALLOW_DATA_URI_IMAGES = true
}
```
-将您的样式表添加到自定义目录中,例如 `custom/public/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
+将您的样式表添加到自定义目录中,例如 `custom/public/assets/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
```html
diff --git a/docs/content/administration/https-support.en-us.md b/docs/content/administration/https-support.en-us.md
index 4e18722ddf1..981a29bd852 100644
--- a/docs/content/administration/https-support.en-us.md
+++ b/docs/content/administration/https-support.en-us.md
@@ -35,7 +35,7 @@ CERT_FILE = cert.pem
KEY_FILE = key.pem
```
-Note that if your certificate is signed by a third party certificate authority (i.e. not self-signed), then cert.pem should contain the certificate chain. The server certificate must be the first entry in cert.pem, followed by the intermediaries in order (if any). The root certificate does not have to be included because the connecting client must already have it in order to estalbish the trust relationship.
+Note that if your certificate is signed by a third party certificate authority (i.e. not self-signed), then cert.pem should contain the certificate chain. The server certificate must be the first entry in cert.pem, followed by the intermediaries in order (if any). The root certificate does not have to be included because the connecting client must already have it in order to establish the trust relationship.
To learn more about the config values, please checkout the [Config Cheat Sheet](administration/config-cheat-sheet.md#server-server).
For the `CERT_FILE` or `KEY_FILE` field, the file path is relative to the `GITEA_CUSTOM` environment variable when it is a relative path. It can be an absolute path as well.
diff --git a/docs/content/administration/mail-templates.en-us.md b/docs/content/administration/mail-templates.en-us.md
index a129453e1a8..b642ff4aa7f 100644
--- a/docs/content/administration/mail-templates.en-us.md
+++ b/docs/content/administration/mail-templates.en-us.md
@@ -85,7 +85,7 @@ Text and macros for the mail body
Specifying a _subject_ section is optional (and therefore also the dash line separator). When used, the separator between
_subject_ and _mail body_ templates requires at least three dashes; no other characters are allowed in the separator line.
-_Subject_ and _mail body_ are parsed by [Golang's template engine](https://golang.org/pkg/text/template/) and
+_Subject_ and _mail body_ are parsed by [Golang's template engine](https://go.dev/pkg/text/template/) and
are provided with a _metadata context_ assembled for each notification. The context contains the following elements:
| Name | Type | Available | Usage |
@@ -110,7 +110,7 @@ All names are case sensitive.
### The _subject_ part of the template
-The template engine used for the mail _subject_ is golang's [`text/template`](https://golang.org/pkg/text/template/).
+The template engine used for the mail _subject_ is golang's [`text/template`](https://go.dev/pkg/text/template/).
Please refer to the linked documentation for details about its syntax.
The _subject_ is built using the following steps:
@@ -138,7 +138,7 @@ the two templates, even if a valid subject template is present.
### The _mail body_ part of the template
-The template engine used for the _mail body_ is golang's [`html/template`](https://golang.org/pkg/html/template/).
+The template engine used for the _mail body_ is golang's [`html/template`](https://go.dev/pkg/html/template/).
Please refer to the linked documentation for details about its syntax.
The _mail body_ is parsed after the mail subject, so there is an additional _metadata_ field which is
@@ -146,7 +146,7 @@ the actual rendered subject, after all considerations.
The expected result is HTML (including structural elements like``, ``, etc.). Styling
through `
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-python.svg b/public/assets/img/svg/gitea-python.svg
index a10bad9e073..68e19ef2be4 100644
--- a/public/assets/img/svg/gitea-python.svg
+++ b/public/assets/img/svg/gitea-python.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-rubygems.svg b/public/assets/img/svg/gitea-rubygems.svg
index 4741b37a7d9..4e43bdf2f43 100644
--- a/public/assets/img/svg/gitea-rubygems.svg
+++ b/public/assets/img/svg/gitea-rubygems.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-split.svg b/public/assets/img/svg/gitea-split.svg
index 56b2ff193da..9ce3077a968 100644
--- a/public/assets/img/svg/gitea-split.svg
+++ b/public/assets/img/svg/gitea-split.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-swift.svg b/public/assets/img/svg/gitea-swift.svg
index 6e93083b536..41821001851 100644
--- a/public/assets/img/svg/gitea-swift.svg
+++ b/public/assets/img/svg/gitea-swift.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-twitter.svg b/public/assets/img/svg/gitea-twitter.svg
index 16598dd43be..5d11c6eaec7 100644
--- a/public/assets/img/svg/gitea-twitter.svg
+++ b/public/assets/img/svg/gitea-twitter.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-vscode.svg b/public/assets/img/svg/gitea-vscode.svg
deleted file mode 100644
index 1d36330524e..00000000000
--- a/public/assets/img/svg/gitea-vscode.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-whitespace.svg b/public/assets/img/svg/gitea-whitespace.svg
index d046d16e0b1..9d3b342b3d4 100644
--- a/public/assets/img/svg/gitea-whitespace.svg
+++ b/public/assets/img/svg/gitea-whitespace.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/gitea-yandex.svg b/public/assets/img/svg/gitea-yandex.svg
index 58f26e12694..d24c0be537a 100644
--- a/public/assets/img/svg/gitea-yandex.svg
+++ b/public/assets/img/svg/gitea-yandex.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-accessibility-inset.svg b/public/assets/img/svg/octicon-accessibility-inset.svg
index 6d5fcd20bd4..2a728a9cf74 100644
--- a/public/assets/img/svg/octicon-accessibility-inset.svg
+++ b/public/assets/img/svg/octicon-accessibility-inset.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-accessibility.svg b/public/assets/img/svg/octicon-accessibility.svg
index e12784f3407..fcd56827f5e 100644
--- a/public/assets/img/svg/octicon-accessibility.svg
+++ b/public/assets/img/svg/octicon-accessibility.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-both.svg b/public/assets/img/svg/octicon-arrow-both.svg
index e5fa8275f80..aec2d6ac08d 100644
--- a/public/assets/img/svg/octicon-arrow-both.svg
+++ b/public/assets/img/svg/octicon-arrow-both.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-down-left.svg b/public/assets/img/svg/octicon-arrow-down-left.svg
index 6001d57767f..720f3208267 100644
--- a/public/assets/img/svg/octicon-arrow-down-left.svg
+++ b/public/assets/img/svg/octicon-arrow-down-left.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-down.svg b/public/assets/img/svg/octicon-arrow-down.svg
index 6fb58b26770..87b526311e7 100644
--- a/public/assets/img/svg/octicon-arrow-down.svg
+++ b/public/assets/img/svg/octicon-arrow-down.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-left.svg b/public/assets/img/svg/octicon-arrow-left.svg
index e347e060058..0e498725bc0 100644
--- a/public/assets/img/svg/octicon-arrow-left.svg
+++ b/public/assets/img/svg/octicon-arrow-left.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-right.svg b/public/assets/img/svg/octicon-arrow-right.svg
index b421c393237..5298ea14219 100644
--- a/public/assets/img/svg/octicon-arrow-right.svg
+++ b/public/assets/img/svg/octicon-arrow-right.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-switch.svg b/public/assets/img/svg/octicon-arrow-switch.svg
index daf1fc001d5..8d1bc1d7aca 100644
--- a/public/assets/img/svg/octicon-arrow-switch.svg
+++ b/public/assets/img/svg/octicon-arrow-switch.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-up-right.svg b/public/assets/img/svg/octicon-arrow-up-right.svg
index 6ba2bf63236..d3c0533c2f6 100644
--- a/public/assets/img/svg/octicon-arrow-up-right.svg
+++ b/public/assets/img/svg/octicon-arrow-up-right.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-arrow-up.svg b/public/assets/img/svg/octicon-arrow-up.svg
index 7b6647f1af2..b790d6e4e20 100644
--- a/public/assets/img/svg/octicon-arrow-up.svg
+++ b/public/assets/img/svg/octicon-arrow-up.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-beaker.svg b/public/assets/img/svg/octicon-beaker.svg
index 7c72b85494e..ce0ad4d8155 100644
--- a/public/assets/img/svg/octicon-beaker.svg
+++ b/public/assets/img/svg/octicon-beaker.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-bell-slash.svg b/public/assets/img/svg/octicon-bell-slash.svg
index 07668ca654c..344671d8a81 100644
--- a/public/assets/img/svg/octicon-bell-slash.svg
+++ b/public/assets/img/svg/octicon-bell-slash.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-bell.svg b/public/assets/img/svg/octicon-bell.svg
index aed30a47652..26903da2b0a 100644
--- a/public/assets/img/svg/octicon-bell.svg
+++ b/public/assets/img/svg/octicon-bell.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-blocked.svg b/public/assets/img/svg/octicon-blocked.svg
index fb38943a639..0d0a7c0b343 100644
--- a/public/assets/img/svg/octicon-blocked.svg
+++ b/public/assets/img/svg/octicon-blocked.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-book.svg b/public/assets/img/svg/octicon-book.svg
index ee48f48a84e..3b58ec1eaad 100644
--- a/public/assets/img/svg/octicon-book.svg
+++ b/public/assets/img/svg/octicon-book.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-bookmark-slash.svg b/public/assets/img/svg/octicon-bookmark-slash.svg
index 79c07ef2a21..781ae92d224 100644
--- a/public/assets/img/svg/octicon-bookmark-slash.svg
+++ b/public/assets/img/svg/octicon-bookmark-slash.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-briefcase.svg b/public/assets/img/svg/octicon-briefcase.svg
index c4a89cb3ac6..3293cc80ed0 100644
--- a/public/assets/img/svg/octicon-briefcase.svg
+++ b/public/assets/img/svg/octicon-briefcase.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-broadcast.svg b/public/assets/img/svg/octicon-broadcast.svg
index 6d999c43e86..e8c9f6d21bf 100644
--- a/public/assets/img/svg/octicon-broadcast.svg
+++ b/public/assets/img/svg/octicon-broadcast.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-bug.svg b/public/assets/img/svg/octicon-bug.svg
index 8245e6cd536..20a09048d5f 100644
--- a/public/assets/img/svg/octicon-bug.svg
+++ b/public/assets/img/svg/octicon-bug.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-cache.svg b/public/assets/img/svg/octicon-cache.svg
index c7bb5cfe3ff..5b8a7924bb0 100644
--- a/public/assets/img/svg/octicon-cache.svg
+++ b/public/assets/img/svg/octicon-cache.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-check-circle-fill.svg b/public/assets/img/svg/octicon-check-circle-fill.svg
index 689d38023cc..8840d55ce1d 100644
--- a/public/assets/img/svg/octicon-check-circle-fill.svg
+++ b/public/assets/img/svg/octicon-check-circle-fill.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-check-circle.svg b/public/assets/img/svg/octicon-check-circle.svg
index 28d99f19b11..63ff6d2b21b 100644
--- a/public/assets/img/svg/octicon-check-circle.svg
+++ b/public/assets/img/svg/octicon-check-circle.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-check.svg b/public/assets/img/svg/octicon-check.svg
index 10dffe55de0..b76500b1312 100644
--- a/public/assets/img/svg/octicon-check.svg
+++ b/public/assets/img/svg/octicon-check.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-checkbox.svg b/public/assets/img/svg/octicon-checkbox.svg
index eebffde2e08..b9711c5509a 100644
--- a/public/assets/img/svg/octicon-checkbox.svg
+++ b/public/assets/img/svg/octicon-checkbox.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-checklist.svg b/public/assets/img/svg/octicon-checklist.svg
index 33ab2c32298..172f13a4331 100644
--- a/public/assets/img/svg/octicon-checklist.svg
+++ b/public/assets/img/svg/octicon-checklist.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-chevron-down.svg b/public/assets/img/svg/octicon-chevron-down.svg
index 84e71ca4d8a..824e4764efb 100644
--- a/public/assets/img/svg/octicon-chevron-down.svg
+++ b/public/assets/img/svg/octicon-chevron-down.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-chevron-left.svg b/public/assets/img/svg/octicon-chevron-left.svg
index a56612a7eb3..ec2e25a9689 100644
--- a/public/assets/img/svg/octicon-chevron-left.svg
+++ b/public/assets/img/svg/octicon-chevron-left.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-chevron-right.svg b/public/assets/img/svg/octicon-chevron-right.svg
index d5a610bd354..4a575153afa 100644
--- a/public/assets/img/svg/octicon-chevron-right.svg
+++ b/public/assets/img/svg/octicon-chevron-right.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-chevron-up.svg b/public/assets/img/svg/octicon-chevron-up.svg
index 952dd484afa..4dac4b5049c 100644
--- a/public/assets/img/svg/octicon-chevron-up.svg
+++ b/public/assets/img/svg/octicon-chevron-up.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-circle-slash.svg b/public/assets/img/svg/octicon-circle-slash.svg
index 1c0a2e03587..fbc3865094c 100644
--- a/public/assets/img/svg/octicon-circle-slash.svg
+++ b/public/assets/img/svg/octicon-circle-slash.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-clock.svg b/public/assets/img/svg/octicon-clock.svg
index ae277ee82f0..186f6fbefc8 100644
--- a/public/assets/img/svg/octicon-clock.svg
+++ b/public/assets/img/svg/octicon-clock.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-cloud-offline.svg b/public/assets/img/svg/octicon-cloud-offline.svg
index 5193a54d281..a4c3091638c 100644
--- a/public/assets/img/svg/octicon-cloud-offline.svg
+++ b/public/assets/img/svg/octicon-cloud-offline.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-cloud.svg b/public/assets/img/svg/octicon-cloud.svg
index 7ff6f5b3eab..38b6a76122d 100644
--- a/public/assets/img/svg/octicon-cloud.svg
+++ b/public/assets/img/svg/octicon-cloud.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-code-of-conduct.svg b/public/assets/img/svg/octicon-code-of-conduct.svg
index 2bf627843ca..20d4152643f 100644
--- a/public/assets/img/svg/octicon-code-of-conduct.svg
+++ b/public/assets/img/svg/octicon-code-of-conduct.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-code-review.svg b/public/assets/img/svg/octicon-code-review.svg
index 595e48eb49a..2ba5e125696 100644
--- a/public/assets/img/svg/octicon-code-review.svg
+++ b/public/assets/img/svg/octicon-code-review.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-code-square.svg b/public/assets/img/svg/octicon-code-square.svg
index a95ca37e162..8dadc44eee3 100644
--- a/public/assets/img/svg/octicon-code-square.svg
+++ b/public/assets/img/svg/octicon-code-square.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-code.svg b/public/assets/img/svg/octicon-code.svg
index 33c920829df..a18c3b6ef6c 100644
--- a/public/assets/img/svg/octicon-code.svg
+++ b/public/assets/img/svg/octicon-code.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-codescan-checkmark.svg b/public/assets/img/svg/octicon-codescan-checkmark.svg
index 773ad56dbea..e81d4e9e53e 100644
--- a/public/assets/img/svg/octicon-codescan-checkmark.svg
+++ b/public/assets/img/svg/octicon-codescan-checkmark.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-codescan.svg b/public/assets/img/svg/octicon-codescan.svg
index ac4b6b4a186..c03a0e582ed 100644
--- a/public/assets/img/svg/octicon-codescan.svg
+++ b/public/assets/img/svg/octicon-codescan.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-comment-discussion.svg b/public/assets/img/svg/octicon-comment-discussion.svg
index 7abc3f135f7..2a2728db042 100644
--- a/public/assets/img/svg/octicon-comment-discussion.svg
+++ b/public/assets/img/svg/octicon-comment-discussion.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-comment.svg b/public/assets/img/svg/octicon-comment.svg
index 63403858791..916d808d9ff 100644
--- a/public/assets/img/svg/octicon-comment.svg
+++ b/public/assets/img/svg/octicon-comment.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-container.svg b/public/assets/img/svg/octicon-container.svg
index 2e6056bf415..c8eeeb16ec0 100644
--- a/public/assets/img/svg/octicon-container.svg
+++ b/public/assets/img/svg/octicon-container.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-copilot-error.svg b/public/assets/img/svg/octicon-copilot-error.svg
index caaf0d5ec3d..d213328a454 100644
--- a/public/assets/img/svg/octicon-copilot-error.svg
+++ b/public/assets/img/svg/octicon-copilot-error.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-copilot-warning.svg b/public/assets/img/svg/octicon-copilot-warning.svg
index e690dfb507d..af5fa66827e 100644
--- a/public/assets/img/svg/octicon-copilot-warning.svg
+++ b/public/assets/img/svg/octicon-copilot-warning.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-copilot.svg b/public/assets/img/svg/octicon-copilot.svg
index 83872243cf6..c23f45407f6 100644
--- a/public/assets/img/svg/octicon-copilot.svg
+++ b/public/assets/img/svg/octicon-copilot.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-cross-reference.svg b/public/assets/img/svg/octicon-cross-reference.svg
index edd42fa473c..80b122b3566 100644
--- a/public/assets/img/svg/octicon-cross-reference.svg
+++ b/public/assets/img/svg/octicon-cross-reference.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-database.svg b/public/assets/img/svg/octicon-database.svg
index 03b5de464f5..cbc9749286c 100644
--- a/public/assets/img/svg/octicon-database.svg
+++ b/public/assets/img/svg/octicon-database.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-desktop-download.svg b/public/assets/img/svg/octicon-desktop-download.svg
index 4b1446a18ec..4ddaa137dee 100644
--- a/public/assets/img/svg/octicon-desktop-download.svg
+++ b/public/assets/img/svg/octicon-desktop-download.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-device-desktop.svg b/public/assets/img/svg/octicon-device-desktop.svg
index 0911947ee13..4a6183688f6 100644
--- a/public/assets/img/svg/octicon-device-desktop.svg
+++ b/public/assets/img/svg/octicon-device-desktop.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-devices.svg b/public/assets/img/svg/octicon-devices.svg
index 178ea74a6f8..84d2a88febe 100644
--- a/public/assets/img/svg/octicon-devices.svg
+++ b/public/assets/img/svg/octicon-devices.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-diamond.svg b/public/assets/img/svg/octicon-diamond.svg
index cc30842ce93..82d0bcbcfe8 100644
--- a/public/assets/img/svg/octicon-diamond.svg
+++ b/public/assets/img/svg/octicon-diamond.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-diff-ignored.svg b/public/assets/img/svg/octicon-diff-ignored.svg
index 47d59486e5b..6949409bdfb 100644
--- a/public/assets/img/svg/octicon-diff-ignored.svg
+++ b/public/assets/img/svg/octicon-diff-ignored.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-diff-renamed.svg b/public/assets/img/svg/octicon-diff-renamed.svg
index f7b9eb2bdb1..f07999a6608 100644
--- a/public/assets/img/svg/octicon-diff-renamed.svg
+++ b/public/assets/img/svg/octicon-diff-renamed.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-discussion-closed.svg b/public/assets/img/svg/octicon-discussion-closed.svg
index 08c17812d76..d97598d2aa2 100644
--- a/public/assets/img/svg/octicon-discussion-closed.svg
+++ b/public/assets/img/svg/octicon-discussion-closed.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-discussion-duplicate.svg b/public/assets/img/svg/octicon-discussion-duplicate.svg
index ed93bf3b0fa..01fd6641e22 100644
--- a/public/assets/img/svg/octicon-discussion-duplicate.svg
+++ b/public/assets/img/svg/octicon-discussion-duplicate.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-discussion-outdated.svg b/public/assets/img/svg/octicon-discussion-outdated.svg
index 388dac3de15..515e63afae7 100644
--- a/public/assets/img/svg/octicon-discussion-outdated.svg
+++ b/public/assets/img/svg/octicon-discussion-outdated.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-download.svg b/public/assets/img/svg/octicon-download.svg
index f5ae4e389dc..80584198301 100644
--- a/public/assets/img/svg/octicon-download.svg
+++ b/public/assets/img/svg/octicon-download.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-eye-closed.svg b/public/assets/img/svg/octicon-eye-closed.svg
index 364777d1688..3b493863cdc 100644
--- a/public/assets/img/svg/octicon-eye-closed.svg
+++ b/public/assets/img/svg/octicon-eye-closed.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-eye.svg b/public/assets/img/svg/octicon-eye.svg
index 101194041ea..c0b3648c630 100644
--- a/public/assets/img/svg/octicon-eye.svg
+++ b/public/assets/img/svg/octicon-eye.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-issue-closed.svg b/public/assets/img/svg/octicon-feed-issue-closed.svg
index ee1d4ff8a7b..9cd3127afc0 100644
--- a/public/assets/img/svg/octicon-feed-issue-closed.svg
+++ b/public/assets/img/svg/octicon-feed-issue-closed.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-issue-draft.svg b/public/assets/img/svg/octicon-feed-issue-draft.svg
index b1f8aca8718..091a59163ed 100644
--- a/public/assets/img/svg/octicon-feed-issue-draft.svg
+++ b/public/assets/img/svg/octicon-feed-issue-draft.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-issue-reopen.svg b/public/assets/img/svg/octicon-feed-issue-reopen.svg
index 1ee31ec368f..c82d5b0fdc6 100644
--- a/public/assets/img/svg/octicon-feed-issue-reopen.svg
+++ b/public/assets/img/svg/octicon-feed-issue-reopen.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-merged.svg b/public/assets/img/svg/octicon-feed-merged.svg
index 47f487167da..2984bef2272 100644
--- a/public/assets/img/svg/octicon-feed-merged.svg
+++ b/public/assets/img/svg/octicon-feed-merged.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-rocket.svg b/public/assets/img/svg/octicon-feed-rocket.svg
index e74dfdddcce..48587a1ab6d 100644
--- a/public/assets/img/svg/octicon-feed-rocket.svg
+++ b/public/assets/img/svg/octicon-feed-rocket.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-star.svg b/public/assets/img/svg/octicon-feed-star.svg
index 70f6859fbad..3c3a6aa48a6 100644
--- a/public/assets/img/svg/octicon-feed-star.svg
+++ b/public/assets/img/svg/octicon-feed-star.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-tag.svg b/public/assets/img/svg/octicon-feed-tag.svg
index 90a796f38b5..d63dd74c4de 100644
--- a/public/assets/img/svg/octicon-feed-tag.svg
+++ b/public/assets/img/svg/octicon-feed-tag.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-feed-trophy.svg b/public/assets/img/svg/octicon-feed-trophy.svg
index ed39c769c03..ba06c3e712d 100644
--- a/public/assets/img/svg/octicon-feed-trophy.svg
+++ b/public/assets/img/svg/octicon-feed-trophy.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-file-added.svg b/public/assets/img/svg/octicon-file-added.svg
index 927784d2e6f..a8cd80f3e8a 100644
--- a/public/assets/img/svg/octicon-file-added.svg
+++ b/public/assets/img/svg/octicon-file-added.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-file-badge.svg b/public/assets/img/svg/octicon-file-badge.svg
index 2f1ad34fd08..5f0a74206ad 100644
--- a/public/assets/img/svg/octicon-file-badge.svg
+++ b/public/assets/img/svg/octicon-file-badge.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-file-binary.svg b/public/assets/img/svg/octicon-file-binary.svg
index f6dd459eed5..492e7d54335 100644
--- a/public/assets/img/svg/octicon-file-binary.svg
+++ b/public/assets/img/svg/octicon-file-binary.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-file-code.svg b/public/assets/img/svg/octicon-file-code.svg
index 38baefd9e98..66430e30822 100644
--- a/public/assets/img/svg/octicon-file-code.svg
+++ b/public/assets/img/svg/octicon-file-code.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-file-symlink-file.svg b/public/assets/img/svg/octicon-file-symlink-file.svg
index 5b1d9aad235..21b8cbf5165 100644
--- a/public/assets/img/svg/octicon-file-symlink-file.svg
+++ b/public/assets/img/svg/octicon-file-symlink-file.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-file-zip.svg b/public/assets/img/svg/octicon-file-zip.svg
index 69e05f00b8f..3adddf0aa16 100644
--- a/public/assets/img/svg/octicon-file-zip.svg
+++ b/public/assets/img/svg/octicon-file-zip.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-file.svg b/public/assets/img/svg/octicon-file.svg
index 976d8d99d3e..faf92c5431c 100644
--- a/public/assets/img/svg/octicon-file.svg
+++ b/public/assets/img/svg/octicon-file.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-filter-remove.svg b/public/assets/img/svg/octicon-filter-remove.svg
index dd968226b17..c10010622bc 100644
--- a/public/assets/img/svg/octicon-filter-remove.svg
+++ b/public/assets/img/svg/octicon-filter-remove.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-flame.svg b/public/assets/img/svg/octicon-flame.svg
index 022f7edd237..0db84ac379d 100644
--- a/public/assets/img/svg/octicon-flame.svg
+++ b/public/assets/img/svg/octicon-flame.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-fold-up.svg b/public/assets/img/svg/octicon-fold-up.svg
index 6429cf4573b..c139cf83620 100644
--- a/public/assets/img/svg/octicon-fold-up.svg
+++ b/public/assets/img/svg/octicon-fold-up.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-gear.svg b/public/assets/img/svg/octicon-gear.svg
index 2da0cff438b..be6eee1b8aa 100644
--- a/public/assets/img/svg/octicon-gear.svg
+++ b/public/assets/img/svg/octicon-gear.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-gift.svg b/public/assets/img/svg/octicon-gift.svg
index dd2b6dda98c..4a6ba3049a1 100644
--- a/public/assets/img/svg/octicon-gift.svg
+++ b/public/assets/img/svg/octicon-gift.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-git-branch.svg b/public/assets/img/svg/octicon-git-branch.svg
index f97a3c96901..c7116adf010 100644
--- a/public/assets/img/svg/octicon-git-branch.svg
+++ b/public/assets/img/svg/octicon-git-branch.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-git-merge.svg b/public/assets/img/svg/octicon-git-merge.svg
index f6081f984ba..17299614771 100644
--- a/public/assets/img/svg/octicon-git-merge.svg
+++ b/public/assets/img/svg/octicon-git-merge.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-git-pull-request-closed.svg b/public/assets/img/svg/octicon-git-pull-request-closed.svg
index 61a903c9180..628f9fe6daa 100644
--- a/public/assets/img/svg/octicon-git-pull-request-closed.svg
+++ b/public/assets/img/svg/octicon-git-pull-request-closed.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-globe.svg b/public/assets/img/svg/octicon-globe.svg
index b4f21b5b035..60ca5b57e37 100644
--- a/public/assets/img/svg/octicon-globe.svg
+++ b/public/assets/img/svg/octicon-globe.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-goal.svg b/public/assets/img/svg/octicon-goal.svg
index 012a4f0c308..dd36a51fb7b 100644
--- a/public/assets/img/svg/octicon-goal.svg
+++ b/public/assets/img/svg/octicon-goal.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-graph.svg b/public/assets/img/svg/octicon-graph.svg
index 075c6afa7b1..393faf95bd1 100644
--- a/public/assets/img/svg/octicon-graph.svg
+++ b/public/assets/img/svg/octicon-graph.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-hash.svg b/public/assets/img/svg/octicon-hash.svg
index 3c74070d2b1..9920504192c 100644
--- a/public/assets/img/svg/octicon-hash.svg
+++ b/public/assets/img/svg/octicon-hash.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-heart-fill.svg b/public/assets/img/svg/octicon-heart-fill.svg
index 0665a168946..1f23ef46da5 100644
--- a/public/assets/img/svg/octicon-heart-fill.svg
+++ b/public/assets/img/svg/octicon-heart-fill.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-heart.svg b/public/assets/img/svg/octicon-heart.svg
index effbee4f768..3980b80b521 100644
--- a/public/assets/img/svg/octicon-heart.svg
+++ b/public/assets/img/svg/octicon-heart.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-history.svg b/public/assets/img/svg/octicon-history.svg
index 006d908d113..fb835dc4aa3 100644
--- a/public/assets/img/svg/octicon-history.svg
+++ b/public/assets/img/svg/octicon-history.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-home.svg b/public/assets/img/svg/octicon-home.svg
index 0ebd98fccb2..2586237e1f8 100644
--- a/public/assets/img/svg/octicon-home.svg
+++ b/public/assets/img/svg/octicon-home.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-hourglass.svg b/public/assets/img/svg/octicon-hourglass.svg
index 815ddcd1c10..8f84421c9e9 100644
--- a/public/assets/img/svg/octicon-hourglass.svg
+++ b/public/assets/img/svg/octicon-hourglass.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-hubot.svg b/public/assets/img/svg/octicon-hubot.svg
index 46418adc08c..00423896482 100644
--- a/public/assets/img/svg/octicon-hubot.svg
+++ b/public/assets/img/svg/octicon-hubot.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-inbox.svg b/public/assets/img/svg/octicon-inbox.svg
index 42cc08fa80d..3d65320ac6a 100644
--- a/public/assets/img/svg/octicon-inbox.svg
+++ b/public/assets/img/svg/octicon-inbox.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-infinity.svg b/public/assets/img/svg/octicon-infinity.svg
index aa94289fed7..2edd1ef91b6 100644
--- a/public/assets/img/svg/octicon-infinity.svg
+++ b/public/assets/img/svg/octicon-infinity.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-issue-closed.svg b/public/assets/img/svg/octicon-issue-closed.svg
index af0422c4463..1d0aa0c2b42 100644
--- a/public/assets/img/svg/octicon-issue-closed.svg
+++ b/public/assets/img/svg/octicon-issue-closed.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-issue-draft.svg b/public/assets/img/svg/octicon-issue-draft.svg
index 5b3f598a957..d02ddd3e0c8 100644
--- a/public/assets/img/svg/octicon-issue-draft.svg
+++ b/public/assets/img/svg/octicon-issue-draft.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-issue-reopened.svg b/public/assets/img/svg/octicon-issue-reopened.svg
index faac17d1199..cd72facc374 100644
--- a/public/assets/img/svg/octicon-issue-reopened.svg
+++ b/public/assets/img/svg/octicon-issue-reopened.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-key.svg b/public/assets/img/svg/octicon-key.svg
index 4b9b2990890..6705b713832 100644
--- a/public/assets/img/svg/octicon-key.svg
+++ b/public/assets/img/svg/octicon-key.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-law.svg b/public/assets/img/svg/octicon-law.svg
index 9d6cfb15af1..841798e644a 100644
--- a/public/assets/img/svg/octicon-law.svg
+++ b/public/assets/img/svg/octicon-law.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-light-bulb.svg b/public/assets/img/svg/octicon-light-bulb.svg
index 1833a06a889..c438ecf25e0 100644
--- a/public/assets/img/svg/octicon-light-bulb.svg
+++ b/public/assets/img/svg/octicon-light-bulb.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-link-external.svg b/public/assets/img/svg/octicon-link-external.svg
index 2adda55d2c4..6d7750b9d26 100644
--- a/public/assets/img/svg/octicon-link-external.svg
+++ b/public/assets/img/svg/octicon-link-external.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-link.svg b/public/assets/img/svg/octicon-link.svg
index e6b60a12f70..9269974e490 100644
--- a/public/assets/img/svg/octicon-link.svg
+++ b/public/assets/img/svg/octicon-link.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-list-ordered.svg b/public/assets/img/svg/octicon-list-ordered.svg
index 93c3f4f453f..004070815a9 100644
--- a/public/assets/img/svg/octicon-list-ordered.svg
+++ b/public/assets/img/svg/octicon-list-ordered.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-location.svg b/public/assets/img/svg/octicon-location.svg
index d4fc960d2ca..7f91acc2e70 100644
--- a/public/assets/img/svg/octicon-location.svg
+++ b/public/assets/img/svg/octicon-location.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-log.svg b/public/assets/img/svg/octicon-log.svg
index 3bc4b5e799f..0f71230f069 100644
--- a/public/assets/img/svg/octicon-log.svg
+++ b/public/assets/img/svg/octicon-log.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-logo-gist.svg b/public/assets/img/svg/octicon-logo-gist.svg
index 14092875268..8621f14776d 100644
--- a/public/assets/img/svg/octicon-logo-gist.svg
+++ b/public/assets/img/svg/octicon-logo-gist.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-logo-github.svg b/public/assets/img/svg/octicon-logo-github.svg
index e17a7ef3fc1..02d92c9b13f 100644
--- a/public/assets/img/svg/octicon-logo-github.svg
+++ b/public/assets/img/svg/octicon-logo-github.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-mail.svg b/public/assets/img/svg/octicon-mail.svg
index 03bb8e5b406..750b742e6c1 100644
--- a/public/assets/img/svg/octicon-mail.svg
+++ b/public/assets/img/svg/octicon-mail.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-mark-github.svg b/public/assets/img/svg/octicon-mark-github.svg
index 3b4322658aa..9381053c066 100644
--- a/public/assets/img/svg/octicon-mark-github.svg
+++ b/public/assets/img/svg/octicon-mark-github.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-megaphone.svg b/public/assets/img/svg/octicon-megaphone.svg
index 14c8c74a60d..178b55092b1 100644
--- a/public/assets/img/svg/octicon-megaphone.svg
+++ b/public/assets/img/svg/octicon-megaphone.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-meter.svg b/public/assets/img/svg/octicon-meter.svg
index e192d5e8e0e..38bd456445e 100644
--- a/public/assets/img/svg/octicon-meter.svg
+++ b/public/assets/img/svg/octicon-meter.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-mirror.svg b/public/assets/img/svg/octicon-mirror.svg
index 05efbf2fc74..d9c67fcf84b 100644
--- a/public/assets/img/svg/octicon-mirror.svg
+++ b/public/assets/img/svg/octicon-mirror.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-moon.svg b/public/assets/img/svg/octicon-moon.svg
index a51e2230348..244544d6c1b 100644
--- a/public/assets/img/svg/octicon-moon.svg
+++ b/public/assets/img/svg/octicon-moon.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-mortar-board.svg b/public/assets/img/svg/octicon-mortar-board.svg
index 6a8d634f0b8..8a9f9545466 100644
--- a/public/assets/img/svg/octicon-mortar-board.svg
+++ b/public/assets/img/svg/octicon-mortar-board.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-move-to-bottom.svg b/public/assets/img/svg/octicon-move-to-bottom.svg
index 10595b106c1..3f2a183df99 100644
--- a/public/assets/img/svg/octicon-move-to-bottom.svg
+++ b/public/assets/img/svg/octicon-move-to-bottom.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-move-to-end.svg b/public/assets/img/svg/octicon-move-to-end.svg
index cd6e3e6ec9a..ef3e60bc3a3 100644
--- a/public/assets/img/svg/octicon-move-to-end.svg
+++ b/public/assets/img/svg/octicon-move-to-end.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-move-to-start.svg b/public/assets/img/svg/octicon-move-to-start.svg
index 14ffc798381..2dc1df7344b 100644
--- a/public/assets/img/svg/octicon-move-to-start.svg
+++ b/public/assets/img/svg/octicon-move-to-start.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-move-to-top.svg b/public/assets/img/svg/octicon-move-to-top.svg
index 05316ded4ac..109515cc52a 100644
--- a/public/assets/img/svg/octicon-move-to-top.svg
+++ b/public/assets/img/svg/octicon-move-to-top.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-mute.svg b/public/assets/img/svg/octicon-mute.svg
index a683c111523..2bb114f8a56 100644
--- a/public/assets/img/svg/octicon-mute.svg
+++ b/public/assets/img/svg/octicon-mute.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-number.svg b/public/assets/img/svg/octicon-number.svg
index 53c4d4e8f75..0a88de18aa8 100644
--- a/public/assets/img/svg/octicon-number.svg
+++ b/public/assets/img/svg/octicon-number.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-organization.svg b/public/assets/img/svg/octicon-organization.svg
index d2c02261057..0799b073110 100644
--- a/public/assets/img/svg/octicon-organization.svg
+++ b/public/assets/img/svg/octicon-organization.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-package-dependencies.svg b/public/assets/img/svg/octicon-package-dependencies.svg
index ae408868b8c..8cb567153ba 100644
--- a/public/assets/img/svg/octicon-package-dependencies.svg
+++ b/public/assets/img/svg/octicon-package-dependencies.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-package-dependents.svg b/public/assets/img/svg/octicon-package-dependents.svg
index bad01efc9b4..22dd4d1626f 100644
--- a/public/assets/img/svg/octicon-package-dependents.svg
+++ b/public/assets/img/svg/octicon-package-dependents.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-package.svg b/public/assets/img/svg/octicon-package.svg
index ce79726ae4b..61b222508c7 100644
--- a/public/assets/img/svg/octicon-package.svg
+++ b/public/assets/img/svg/octicon-package.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-paintbrush.svg b/public/assets/img/svg/octicon-paintbrush.svg
index 8cbfcf3ee4a..d9ac07654c6 100644
--- a/public/assets/img/svg/octicon-paintbrush.svg
+++ b/public/assets/img/svg/octicon-paintbrush.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-paperclip.svg b/public/assets/img/svg/octicon-paperclip.svg
index 326c8b8c3ff..de38702a44f 100644
--- a/public/assets/img/svg/octicon-paperclip.svg
+++ b/public/assets/img/svg/octicon-paperclip.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-passkey-fill.svg b/public/assets/img/svg/octicon-passkey-fill.svg
index 2394d3eaaf2..98fcafb7722 100644
--- a/public/assets/img/svg/octicon-passkey-fill.svg
+++ b/public/assets/img/svg/octicon-passkey-fill.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-paste.svg b/public/assets/img/svg/octicon-paste.svg
index 13b4d7430dc..212c2b82aa6 100644
--- a/public/assets/img/svg/octicon-paste.svg
+++ b/public/assets/img/svg/octicon-paste.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-pencil.svg b/public/assets/img/svg/octicon-pencil.svg
index 41c638fcedb..f0f1f7387cc 100644
--- a/public/assets/img/svg/octicon-pencil.svg
+++ b/public/assets/img/svg/octicon-pencil.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-people.svg b/public/assets/img/svg/octicon-people.svg
index 9bf6400b649..9143c70a46d 100644
--- a/public/assets/img/svg/octicon-people.svg
+++ b/public/assets/img/svg/octicon-people.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-person-add.svg b/public/assets/img/svg/octicon-person-add.svg
index 6dcdd20d830..4c9517269d3 100644
--- a/public/assets/img/svg/octicon-person-add.svg
+++ b/public/assets/img/svg/octicon-person-add.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-person-fill.svg b/public/assets/img/svg/octicon-person-fill.svg
index e3cd5eedf05..4715c29f03b 100644
--- a/public/assets/img/svg/octicon-person-fill.svg
+++ b/public/assets/img/svg/octicon-person-fill.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-person.svg b/public/assets/img/svg/octicon-person.svg
index 92aa6987642..2d12f023779 100644
--- a/public/assets/img/svg/octicon-person.svg
+++ b/public/assets/img/svg/octicon-person.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-pin-slash.svg b/public/assets/img/svg/octicon-pin-slash.svg
index 4cd88d58a14..adf7ed4a7ab 100644
--- a/public/assets/img/svg/octicon-pin-slash.svg
+++ b/public/assets/img/svg/octicon-pin-slash.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-pin.svg b/public/assets/img/svg/octicon-pin.svg
index e24d9cc817d..49ac5af31ac 100644
--- a/public/assets/img/svg/octicon-pin.svg
+++ b/public/assets/img/svg/octicon-pin.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-pivot-column.svg b/public/assets/img/svg/octicon-pivot-column.svg
index 34370c20600..795fde10d14 100644
--- a/public/assets/img/svg/octicon-pivot-column.svg
+++ b/public/assets/img/svg/octicon-pivot-column.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-plug.svg b/public/assets/img/svg/octicon-plug.svg
index aac41be9449..4caf972b676 100644
--- a/public/assets/img/svg/octicon-plug.svg
+++ b/public/assets/img/svg/octicon-plug.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-project-template.svg b/public/assets/img/svg/octicon-project-template.svg
index 559465e7dcd..31d4cc06b7e 100644
--- a/public/assets/img/svg/octicon-project-template.svg
+++ b/public/assets/img/svg/octicon-project-template.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-pulse.svg b/public/assets/img/svg/octicon-pulse.svg
index 5b393ebaa86..2450ffeabe8 100644
--- a/public/assets/img/svg/octicon-pulse.svg
+++ b/public/assets/img/svg/octicon-pulse.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-question.svg b/public/assets/img/svg/octicon-question.svg
index fbc075a2db8..6d6a3f53537 100644
--- a/public/assets/img/svg/octicon-question.svg
+++ b/public/assets/img/svg/octicon-question.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-read.svg b/public/assets/img/svg/octicon-read.svg
index dc27b12ae2f..cd5ee2028d8 100644
--- a/public/assets/img/svg/octicon-read.svg
+++ b/public/assets/img/svg/octicon-read.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-redo.svg b/public/assets/img/svg/octicon-redo.svg
index a4fbeabeaa2..a81a32131f2 100644
--- a/public/assets/img/svg/octicon-redo.svg
+++ b/public/assets/img/svg/octicon-redo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-rel-file-path.svg b/public/assets/img/svg/octicon-rel-file-path.svg
index d7617487e12..45837c2cd39 100644
--- a/public/assets/img/svg/octicon-rel-file-path.svg
+++ b/public/assets/img/svg/octicon-rel-file-path.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-reply.svg b/public/assets/img/svg/octicon-reply.svg
index 124511dae2f..70e550e5d76 100644
--- a/public/assets/img/svg/octicon-reply.svg
+++ b/public/assets/img/svg/octicon-reply.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-repo-clone.svg b/public/assets/img/svg/octicon-repo-clone.svg
index a34eb677c07..67099d8170e 100644
--- a/public/assets/img/svg/octicon-repo-clone.svg
+++ b/public/assets/img/svg/octicon-repo-clone.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-repo-deleted.svg b/public/assets/img/svg/octicon-repo-deleted.svg
index c2f0fc8cf89..c79e3be28c9 100644
--- a/public/assets/img/svg/octicon-repo-deleted.svg
+++ b/public/assets/img/svg/octicon-repo-deleted.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-repo-locked.svg b/public/assets/img/svg/octicon-repo-locked.svg
index 34aa27d5cba..c6def5b5142 100644
--- a/public/assets/img/svg/octicon-repo-locked.svg
+++ b/public/assets/img/svg/octicon-repo-locked.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-repo-pull.svg b/public/assets/img/svg/octicon-repo-pull.svg
index 8cee37524f6..4f02f6f127f 100644
--- a/public/assets/img/svg/octicon-repo-pull.svg
+++ b/public/assets/img/svg/octicon-repo-pull.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-repo-push.svg b/public/assets/img/svg/octicon-repo-push.svg
index f473dcb2b9f..07bd7626e27 100644
--- a/public/assets/img/svg/octicon-repo-push.svg
+++ b/public/assets/img/svg/octicon-repo-push.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-repo-template.svg b/public/assets/img/svg/octicon-repo-template.svg
index 05b951e6413..45b7acf4f9a 100644
--- a/public/assets/img/svg/octicon-repo-template.svg
+++ b/public/assets/img/svg/octicon-repo-template.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-repo.svg b/public/assets/img/svg/octicon-repo.svg
index c237a5f1f47..ace4a3c72d1 100644
--- a/public/assets/img/svg/octicon-repo.svg
+++ b/public/assets/img/svg/octicon-repo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-report.svg b/public/assets/img/svg/octicon-report.svg
index fc678f7c1f9..e0cf5651363 100644
--- a/public/assets/img/svg/octicon-report.svg
+++ b/public/assets/img/svg/octicon-report.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-rocket.svg b/public/assets/img/svg/octicon-rocket.svg
index 967b2d63f41..13395eb5001 100644
--- a/public/assets/img/svg/octicon-rocket.svg
+++ b/public/assets/img/svg/octicon-rocket.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-rss.svg b/public/assets/img/svg/octicon-rss.svg
index 757ec69e13f..6b1303340aa 100644
--- a/public/assets/img/svg/octicon-rss.svg
+++ b/public/assets/img/svg/octicon-rss.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-ruby.svg b/public/assets/img/svg/octicon-ruby.svg
index b4f9f426f4b..3697948a685 100644
--- a/public/assets/img/svg/octicon-ruby.svg
+++ b/public/assets/img/svg/octicon-ruby.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-search.svg b/public/assets/img/svg/octicon-search.svg
index 2623d5d5946..5286c0440c9 100644
--- a/public/assets/img/svg/octicon-search.svg
+++ b/public/assets/img/svg/octicon-search.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-server.svg b/public/assets/img/svg/octicon-server.svg
index 7943abf27c2..fd4e9be060a 100644
--- a/public/assets/img/svg/octicon-server.svg
+++ b/public/assets/img/svg/octicon-server.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-share-android.svg b/public/assets/img/svg/octicon-share-android.svg
index 78b3464c8da..2e1cdcbde50 100644
--- a/public/assets/img/svg/octicon-share-android.svg
+++ b/public/assets/img/svg/octicon-share-android.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-shield-check.svg b/public/assets/img/svg/octicon-shield-check.svg
index 68c653adaeb..99fc924261e 100644
--- a/public/assets/img/svg/octicon-shield-check.svg
+++ b/public/assets/img/svg/octicon-shield-check.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-shield-lock.svg b/public/assets/img/svg/octicon-shield-lock.svg
index ef96e9a1e8e..2057bbc8f93 100644
--- a/public/assets/img/svg/octicon-shield-lock.svg
+++ b/public/assets/img/svg/octicon-shield-lock.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-shield-slash.svg b/public/assets/img/svg/octicon-shield-slash.svg
index eb9360fbdf0..28a2b858471 100644
--- a/public/assets/img/svg/octicon-shield-slash.svg
+++ b/public/assets/img/svg/octicon-shield-slash.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-shield-x.svg b/public/assets/img/svg/octicon-shield-x.svg
index a87b9c189cb..b0fff660353 100644
--- a/public/assets/img/svg/octicon-shield-x.svg
+++ b/public/assets/img/svg/octicon-shield-x.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-shield.svg b/public/assets/img/svg/octicon-shield.svg
index 668979d7e5e..865238e563f 100644
--- a/public/assets/img/svg/octicon-shield.svg
+++ b/public/assets/img/svg/octicon-shield.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sidebar-expand.svg b/public/assets/img/svg/octicon-sidebar-expand.svg
index 42816121a6e..41e5c800f17 100644
--- a/public/assets/img/svg/octicon-sidebar-expand.svg
+++ b/public/assets/img/svg/octicon-sidebar-expand.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sign-in.svg b/public/assets/img/svg/octicon-sign-in.svg
index f44aa4a614a..308c1187830 100644
--- a/public/assets/img/svg/octicon-sign-in.svg
+++ b/public/assets/img/svg/octicon-sign-in.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sign-out.svg b/public/assets/img/svg/octicon-sign-out.svg
index b703ae8b569..ac1c95f9939 100644
--- a/public/assets/img/svg/octicon-sign-out.svg
+++ b/public/assets/img/svg/octicon-sign-out.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-skip.svg b/public/assets/img/svg/octicon-skip.svg
index 778827da664..fe8d37792ee 100644
--- a/public/assets/img/svg/octicon-skip.svg
+++ b/public/assets/img/svg/octicon-skip.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-smiley.svg b/public/assets/img/svg/octicon-smiley.svg
index 0b3d8c9769c..11c9055fd03 100644
--- a/public/assets/img/svg/octicon-smiley.svg
+++ b/public/assets/img/svg/octicon-smiley.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sort-asc.svg b/public/assets/img/svg/octicon-sort-asc.svg
index 45d95c49e23..76fe377cb86 100644
--- a/public/assets/img/svg/octicon-sort-asc.svg
+++ b/public/assets/img/svg/octicon-sort-asc.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sparkle-fill.svg b/public/assets/img/svg/octicon-sparkle-fill.svg
index 3b8a7d276f3..fafd3d839e8 100644
--- a/public/assets/img/svg/octicon-sparkle-fill.svg
+++ b/public/assets/img/svg/octicon-sparkle-fill.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sponsor-tiers.svg b/public/assets/img/svg/octicon-sponsor-tiers.svg
index a5afb85e23c..efe96cd5a8c 100644
--- a/public/assets/img/svg/octicon-sponsor-tiers.svg
+++ b/public/assets/img/svg/octicon-sponsor-tiers.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-squirrel.svg b/public/assets/img/svg/octicon-squirrel.svg
index 4d04ca8061d..60b3ba5c584 100644
--- a/public/assets/img/svg/octicon-squirrel.svg
+++ b/public/assets/img/svg/octicon-squirrel.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-stack.svg b/public/assets/img/svg/octicon-stack.svg
index 683c6c4e2d0..a86dbbee8d1 100644
--- a/public/assets/img/svg/octicon-stack.svg
+++ b/public/assets/img/svg/octicon-stack.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-star.svg b/public/assets/img/svg/octicon-star.svg
index 31e008b2811..2001b8c0e86 100644
--- a/public/assets/img/svg/octicon-star.svg
+++ b/public/assets/img/svg/octicon-star.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-stop.svg b/public/assets/img/svg/octicon-stop.svg
index 6838a7cead1..d85c7a35c2c 100644
--- a/public/assets/img/svg/octicon-stop.svg
+++ b/public/assets/img/svg/octicon-stop.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-stopwatch.svg b/public/assets/img/svg/octicon-stopwatch.svg
index 6e1588ac2e0..eeec8dc396a 100644
--- a/public/assets/img/svg/octicon-stopwatch.svg
+++ b/public/assets/img/svg/octicon-stopwatch.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-strikethrough.svg b/public/assets/img/svg/octicon-strikethrough.svg
index 4a45dfc41d4..1a9c4a0d3ee 100644
--- a/public/assets/img/svg/octicon-strikethrough.svg
+++ b/public/assets/img/svg/octicon-strikethrough.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sun.svg b/public/assets/img/svg/octicon-sun.svg
index f54a4163270..07756fa4386 100644
--- a/public/assets/img/svg/octicon-sun.svg
+++ b/public/assets/img/svg/octicon-sun.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sync.svg b/public/assets/img/svg/octicon-sync.svg
index 8b4d0c8c1e5..30917e16b5b 100644
--- a/public/assets/img/svg/octicon-sync.svg
+++ b/public/assets/img/svg/octicon-sync.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-tag.svg b/public/assets/img/svg/octicon-tag.svg
index 9aaad7f5fea..0de0f6b4fe0 100644
--- a/public/assets/img/svg/octicon-tag.svg
+++ b/public/assets/img/svg/octicon-tag.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-tasklist.svg b/public/assets/img/svg/octicon-tasklist.svg
index 227cd8a61bc..b1067aefc32 100644
--- a/public/assets/img/svg/octicon-tasklist.svg
+++ b/public/assets/img/svg/octicon-tasklist.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-telescope-fill.svg b/public/assets/img/svg/octicon-telescope-fill.svg
index c1961c33235..4debe301d6f 100644
--- a/public/assets/img/svg/octicon-telescope-fill.svg
+++ b/public/assets/img/svg/octicon-telescope-fill.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-telescope.svg b/public/assets/img/svg/octicon-telescope.svg
index 9ca9a6b372b..17e79115ff2 100644
--- a/public/assets/img/svg/octicon-telescope.svg
+++ b/public/assets/img/svg/octicon-telescope.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-terminal.svg b/public/assets/img/svg/octicon-terminal.svg
index 065ef103930..e6349af701b 100644
--- a/public/assets/img/svg/octicon-terminal.svg
+++ b/public/assets/img/svg/octicon-terminal.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-thumbsdown.svg b/public/assets/img/svg/octicon-thumbsdown.svg
index 2d9f4d8afc1..f64457ec516 100644
--- a/public/assets/img/svg/octicon-thumbsdown.svg
+++ b/public/assets/img/svg/octicon-thumbsdown.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-thumbsup.svg b/public/assets/img/svg/octicon-thumbsup.svg
index 7871ab71dd3..1afc4ba99b2 100644
--- a/public/assets/img/svg/octicon-thumbsup.svg
+++ b/public/assets/img/svg/octicon-thumbsup.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-tools.svg b/public/assets/img/svg/octicon-tools.svg
index 8b051eb3fd0..851c44f2eaf 100644
--- a/public/assets/img/svg/octicon-tools.svg
+++ b/public/assets/img/svg/octicon-tools.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-tracked-by-closed-completed.svg b/public/assets/img/svg/octicon-tracked-by-closed-completed.svg
index b12638d87d0..c906f0eb6c5 100644
--- a/public/assets/img/svg/octicon-tracked-by-closed-completed.svg
+++ b/public/assets/img/svg/octicon-tracked-by-closed-completed.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-tracked-by-closed-not-planned.svg b/public/assets/img/svg/octicon-tracked-by-closed-not-planned.svg
index e96e93a40b9..f7383984287 100644
--- a/public/assets/img/svg/octicon-tracked-by-closed-not-planned.svg
+++ b/public/assets/img/svg/octicon-tracked-by-closed-not-planned.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-trash.svg b/public/assets/img/svg/octicon-trash.svg
index e0a1e569090..b52a439af77 100644
--- a/public/assets/img/svg/octicon-trash.svg
+++ b/public/assets/img/svg/octicon-trash.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-triangle-down.svg b/public/assets/img/svg/octicon-triangle-down.svg
index fd1dce1fe82..e8034484fcb 100644
--- a/public/assets/img/svg/octicon-triangle-down.svg
+++ b/public/assets/img/svg/octicon-triangle-down.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-triangle-left.svg b/public/assets/img/svg/octicon-triangle-left.svg
index 66343551a92..fde4d16bad4 100644
--- a/public/assets/img/svg/octicon-triangle-left.svg
+++ b/public/assets/img/svg/octicon-triangle-left.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-triangle-right.svg b/public/assets/img/svg/octicon-triangle-right.svg
index 7b39a67e6e6..48a009760ee 100644
--- a/public/assets/img/svg/octicon-triangle-right.svg
+++ b/public/assets/img/svg/octicon-triangle-right.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-triangle-up.svg b/public/assets/img/svg/octicon-triangle-up.svg
index f4b386b1756..1982cc84c48 100644
--- a/public/assets/img/svg/octicon-triangle-up.svg
+++ b/public/assets/img/svg/octicon-triangle-up.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-trophy.svg b/public/assets/img/svg/octicon-trophy.svg
index ae33cff336b..62c37e70d41 100644
--- a/public/assets/img/svg/octicon-trophy.svg
+++ b/public/assets/img/svg/octicon-trophy.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-typography.svg b/public/assets/img/svg/octicon-typography.svg
index 0b2088ed2fb..3ed9d8ff68b 100644
--- a/public/assets/img/svg/octicon-typography.svg
+++ b/public/assets/img/svg/octicon-typography.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-undo.svg b/public/assets/img/svg/octicon-undo.svg
index 53ac6464147..7dd709241ef 100644
--- a/public/assets/img/svg/octicon-undo.svg
+++ b/public/assets/img/svg/octicon-undo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-unlink.svg b/public/assets/img/svg/octicon-unlink.svg
index 778d5af83cf..a585e8ba909 100644
--- a/public/assets/img/svg/octicon-unlink.svg
+++ b/public/assets/img/svg/octicon-unlink.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-unmute.svg b/public/assets/img/svg/octicon-unmute.svg
index 6ebdf38d148..f471217b63f 100644
--- a/public/assets/img/svg/octicon-unmute.svg
+++ b/public/assets/img/svg/octicon-unmute.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-unread.svg b/public/assets/img/svg/octicon-unread.svg
index 82982ef5e3c..f2c41413b12 100644
--- a/public/assets/img/svg/octicon-unread.svg
+++ b/public/assets/img/svg/octicon-unread.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-unverified.svg b/public/assets/img/svg/octicon-unverified.svg
index f91e66c8aff..5833f2316ac 100644
--- a/public/assets/img/svg/octicon-unverified.svg
+++ b/public/assets/img/svg/octicon-unverified.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-upload.svg b/public/assets/img/svg/octicon-upload.svg
index 16e3f04222f..c61e952511e 100644
--- a/public/assets/img/svg/octicon-upload.svg
+++ b/public/assets/img/svg/octicon-upload.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-verified.svg b/public/assets/img/svg/octicon-verified.svg
index a6de1a30d69..57fc6a0eab6 100644
--- a/public/assets/img/svg/octicon-verified.svg
+++ b/public/assets/img/svg/octicon-verified.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-versions.svg b/public/assets/img/svg/octicon-versions.svg
index b37de6aebee..7f5428055b4 100644
--- a/public/assets/img/svg/octicon-versions.svg
+++ b/public/assets/img/svg/octicon-versions.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-x-circle-fill.svg b/public/assets/img/svg/octicon-x-circle-fill.svg
index 3688189a458..c0a63078849 100644
--- a/public/assets/img/svg/octicon-x-circle-fill.svg
+++ b/public/assets/img/svg/octicon-x-circle-fill.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-x-circle.svg b/public/assets/img/svg/octicon-x-circle.svg
index e4beb382fae..94dc6b92817 100644
--- a/public/assets/img/svg/octicon-x-circle.svg
+++ b/public/assets/img/svg/octicon-x-circle.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-x.svg b/public/assets/img/svg/octicon-x.svg
index a39b8e57b6a..2d78857db26 100644
--- a/public/assets/img/svg/octicon-x.svg
+++ b/public/assets/img/svg/octicon-x.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/svg/octicon-zap.svg b/public/assets/img/svg/octicon-zap.svg
index f711e01eb14..a693cb6d958 100644
--- a/public/assets/img/svg/octicon-zap.svg
+++ b/public/assets/img/svg/octicon-zap.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 4b89d809996..bef41d6266c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,8 +8,8 @@ authors = []
python = "^3.8"
[tool.poetry.group.dev.dependencies]
-djlint = "1.34.0"
-yamllint = "1.33.0"
+djlint = "1.34.1"
+yamllint = "1.35.0"
[tool.djlint]
profile="golang"
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 5411237103a..d530e9cee56 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -71,7 +71,6 @@ import (
"code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -79,6 +78,8 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
web_types "code.gitea.io/gitea/modules/web/types"
+ actions_service "code.gitea.io/gitea/services/actions"
+ "code.gitea.io/gitea/services/context"
)
const artifactRouteBase = "/_apis/pipelines/workflows/{run_id}/artifacts"
@@ -138,12 +139,33 @@ func ArtifactContexter() func(next http.Handler) http.Handler {
return
}
- authToken := strings.TrimPrefix(authHeader, "Bearer ")
- task, err := actions.GetRunningTaskByToken(req.Context(), authToken)
- if err != nil {
- log.Error("Error runner api getting task: %v", err)
- ctx.Error(http.StatusInternalServerError, "Error runner api getting task")
- return
+ // New act_runner uses jwt to authenticate
+ tID, err := actions_service.ParseAuthorizationToken(req)
+
+ var task *actions.ActionTask
+ if err == nil {
+
+ task, err = actions.GetTaskByID(req.Context(), tID)
+ if err != nil {
+ log.Error("Error runner api getting task by ID: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task by ID")
+ return
+ }
+ if task.Status != actions.StatusRunning {
+ log.Error("Error runner api getting task: task is not running")
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running")
+ return
+ }
+ } else {
+ // Old act_runner uses GITEA_TOKEN to authenticate
+ authToken := strings.TrimPrefix(authHeader, "Bearer ")
+
+ task, err = actions.GetRunningTaskByToken(req.Context(), authToken)
+ if err != nil {
+ log.Error("Error runner api getting task: %v", err)
+ ctx.Error(http.StatusInternalServerError, "Error runner api getting task")
+ return
+ }
}
if err := task.LoadJob(req.Context()); err != nil {
@@ -258,8 +280,11 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
return
}
- // update artifact size if zero
- if artifact.FileSize == 0 || artifact.FileCompressedSize == 0 {
+ // update artifact size if zero or not match, over write artifact size
+ if artifact.FileSize == 0 ||
+ artifact.FileCompressedSize == 0 ||
+ artifact.FileSize != fileRealTotalSize ||
+ artifact.FileCompressedSize != chunksTotalSize {
artifact.FileSize = fileRealTotalSize
artifact.FileCompressedSize = chunksTotalSize
artifact.ContentEncoding = ctx.Req.Header.Get("Content-Encoding")
@@ -268,6 +293,8 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
ctx.Error(http.StatusInternalServerError, "Error update artifact")
return
}
+ log.Debug("[artifact] update artifact size, artifact_id: %d, size: %d, compressed size: %d",
+ artifact.ID, artifact.FileSize, artifact.FileCompressedSize)
}
ctx.JSON(http.StatusOK, map[string]string{
@@ -400,7 +427,19 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
var items []downloadArtifactResponseItem
for _, artifact := range artifacts {
- downloadURL := ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
+ var downloadURL string
+ if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
+ u, err := ar.fs.URL(artifact.StoragePath, artifact.ArtifactName)
+ if err != nil && !errors.Is(err, storage.ErrURLNotSupported) {
+ log.Error("Error getting serve direct url: %v", err)
+ }
+ if u != nil {
+ downloadURL = u.String()
+ }
+ }
+ if downloadURL == "" {
+ downloadURL = ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
+ }
item := downloadArtifactResponseItem{
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
ItemType: "file",
@@ -423,15 +462,15 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
}
artifactID := ctx.ParamsInt64("artifact_id")
- artifact, err := actions.GetArtifactByID(ctx, artifactID)
- if errors.Is(err, util.ErrNotExist) {
- log.Error("Error getting artifact: %v", err)
- ctx.Error(http.StatusNotFound, err.Error())
- return
- } else if err != nil {
+ artifact, exist, err := db.GetByID[actions.ActionArtifact](ctx, artifactID)
+ if err != nil {
log.Error("Error getting artifact: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
return
+ } else if !exist {
+ log.Error("artifact with ID %d does not exist", artifactID)
+ ctx.Error(http.StatusNotFound, fmt.Sprintf("artifact with ID %d does not exist", artifactID))
+ return
}
if artifact.RunID != runID {
log.Error("Error dismatch runID and artifactID, task: %v, artifact: %v", runID, artifactID)
diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go
index c7ab70afa95..0713c8bba8c 100644
--- a/routers/api/actions/artifacts_chunks.go
+++ b/routers/api/actions/artifacts_chunks.go
@@ -26,10 +26,11 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
contentRange := ctx.Req.Header.Get("Content-Range")
start, end, length := int64(0), int64(0), int64(0)
if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil {
+ log.Warn("parse content range error: %v, content-range: %s", err, contentRange)
return -1, fmt.Errorf("parse content range error: %v", err)
}
// build chunk store path
- storagePath := fmt.Sprintf("tmp%d/%d-%d-%d.chunk", runID, artifact.ID, start, end)
+ storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
// use io.TeeReader to avoid reading all body to md5 sum.
// it writes data to hasher after reading end
// if hash is not matched, delete the read-end result
@@ -58,6 +59,7 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
}
type chunkFileItem struct {
+ RunID int64
ArtifactID int64
Start int64
End int64
@@ -67,9 +69,12 @@ type chunkFileItem struct {
func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) {
storageDir := fmt.Sprintf("tmp%d", runID)
var chunks []*chunkFileItem
- if err := st.IterateObjects(storageDir, func(path string, obj storage.Object) error {
- item := chunkFileItem{Path: path}
- if _, err := fmt.Sscanf(path, filepath.Join(storageDir, "%d-%d-%d.chunk"), &item.ArtifactID, &item.Start, &item.End); err != nil {
+ if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error {
+ baseName := filepath.Base(fpath)
+ // when read chunks from storage, it only contains storage dir and basename,
+ // no matter the subdirectory setting in storage config
+ item := chunkFileItem{Path: storageDir + "/" + baseName}
+ if _, err := fmt.Sscanf(baseName, "%d-%d-%d-%d.chunk", &item.RunID, &item.ArtifactID, &item.Start, &item.End); err != nil {
return fmt.Errorf("parse content range error: %v", err)
}
chunks = append(chunks, &item)
@@ -181,7 +186,14 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
}()
// save storage path to artifact
- log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath)
+ log.Debug("[artifact] merge chunks to artifact: %d, %s, old:%s", artifact.ID, storagePath, artifact.StoragePath)
+ // if artifact is already uploaded, delete the old file
+ if artifact.StoragePath != "" {
+ if err := st.Delete(artifact.StoragePath); err != nil {
+ log.Warn("Error deleting old artifact: %s, %v", artifact.StoragePath, err)
+ }
+ }
+
artifact.StoragePath = storagePath
artifact.Status = int64(actions.ArtifactStatusUploadConfirmed)
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go
index bf913f2c057..a7cb31288ca 100644
--- a/routers/api/actions/runner/utils.go
+++ b/routers/api/actions/runner/utils.go
@@ -94,6 +94,12 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s
func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
variables := map[string]string{}
+ // Global
+ globalVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{})
+ if err != nil {
+ log.Error("find global variables: %v", err)
+ }
+
// Org / User level
ownerVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID})
if err != nil {
@@ -106,8 +112,8 @@ func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map
log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err)
}
- // Level precedence: Repo > Org / User
- for _, v := range append(ownerVariables, repoVariables...) {
+ // Level precedence: Repo > Org / User > Global
+ for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) {
variables[v.Name] = v.Data
}
@@ -145,6 +151,11 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
refName := git.RefName(ref)
+ giteaRuntimeToken, err := actions.CreateAuthorizationToken(t.ID, t.Job.RunID, t.JobID)
+ if err != nil {
+ log.Error("actions.CreateAuthorizationToken failed: %v", err)
+ }
+
taskContext, err := structpb.NewStruct(map[string]any{
// standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
"action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.
@@ -184,6 +195,7 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
// additional contexts
"gitea_default_actions_url": setting.Actions.DefaultActionsURL.URL(),
+ "gitea_runtime_token": giteaRuntimeToken,
})
if err != nil {
log.Error("structpb.NewStruct failed: %v", err)
diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go
index bb14c5163a4..dae9c3dfcb2 100644
--- a/routers/api/packages/alpine/alpine.go
+++ b/routers/api/packages/alpine/alpine.go
@@ -14,12 +14,12 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages"
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
alpine_service "code.gitea.io/gitea/services/packages/alpine"
)
@@ -72,7 +72,7 @@ func GetRepositoryFile(ctx *context.Context) {
ctx,
pv,
&packages_service.PackageFileInfo{
- Filename: alpine_service.IndexFilename,
+ Filename: alpine_service.IndexArchiveFilename,
CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
},
)
@@ -182,19 +182,38 @@ func UploadPackageFile(ctx *context.Context) {
}
func DownloadPackageFile(ctx *context.Context) {
- pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
+ branch := ctx.Params("branch")
+ repository := ctx.Params("repository")
+ architecture := ctx.Params("architecture")
+
+ opts := &packages_model.PackageFileSearchOptions{
OwnerID: ctx.Package.Owner.ID,
PackageType: packages_model.TypeAlpine,
Query: ctx.Params("filename"),
- CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
- })
+ CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
+ }
+ pfs, _, err := packages_model.SearchFiles(ctx, opts)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
- if len(pfs) != 1 {
- apiError(ctx, http.StatusNotFound, nil)
- return
+ if len(pfs) == 0 {
+ // Try again with architecture 'noarch'
+ if architecture == alpine_module.NoArch {
+ apiError(ctx, http.StatusNotFound, nil)
+ return
+ }
+
+ opts.CompositeKey = fmt.Sprintf("%s|%s|%s", branch, repository, alpine_module.NoArch)
+ if pfs, _, err = packages_model.SearchFiles(ctx, opts); err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
+ return
+ }
+
+ if len(pfs) == 0 {
+ apiError(ctx, http.StatusNotFound, nil)
+ return
+ }
}
s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index 2ba35e21380..5e3cbac8f9c 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -10,7 +10,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
@@ -36,7 +35,7 @@ import (
"code.gitea.io/gitea/routers/api/packages/swift"
"code.gitea.io/gitea/routers/api/packages/vagrant"
"code.gitea.io/gitea/services/auth"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
)
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
@@ -513,14 +512,75 @@ func CommonRoutes() *web.Route {
r.Get("/simple/{id}", pypi.PackageMetadata)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/rpm", func() {
- r.Get(".repo", rpm.GetRepositoryConfig)
- r.Get("/repository.key", rpm.GetRepositoryKey)
- r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
- r.Group("/package/{name}/{version}/{architecture}", func() {
- r.Get("", rpm.DownloadPackageFile)
- r.Delete("", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
+ r.Group("/repository.key", func() {
+ r.Head("", rpm.GetRepositoryKey)
+ r.Get("", rpm.GetRepositoryKey)
+ })
+
+ var (
+ repoPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
+ uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
+ filePattern = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
+ repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
+ )
+
+ r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
+ path := ctx.Params("*")
+ isHead := ctx.Req.Method == "HEAD"
+ isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
+ isPut := ctx.Req.Method == "PUT"
+ isDelete := ctx.Req.Method == "DELETE"
+
+ m := repoPattern.FindStringSubmatch(path)
+ if len(m) == 2 && isGetHead {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ rpm.GetRepositoryConfig(ctx)
+ return
+ }
+
+ m = repoFilePattern.FindStringSubmatch(path)
+ if len(m) == 3 && isGetHead {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ ctx.SetParams("filename", m[2])
+ if isHead {
+ rpm.CheckRepositoryFileExistence(ctx)
+ } else {
+ rpm.GetRepositoryFile(ctx)
+ }
+ return
+ }
+
+ m = uploadPattern.FindStringSubmatch(path)
+ if len(m) == 2 && isPut {
+ reqPackageAccess(perm.AccessModeWrite)(ctx)
+ if ctx.Written() {
+ return
+ }
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ rpm.UploadPackageFile(ctx)
+ return
+ }
+
+ m = filePattern.FindStringSubmatch(path)
+ if len(m) == 6 && (isGetHead || isDelete) {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ ctx.SetParams("name", m[2])
+ ctx.SetParams("version", m[3])
+ ctx.SetParams("architecture", m[4])
+ if isGetHead {
+ rpm.DownloadPackageFile(ctx)
+ } else {
+ reqPackageAccess(perm.AccessModeWrite)(ctx)
+ if ctx.Written() {
+ return
+ }
+ rpm.DeletePackageFile(ctx)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNotFound)
})
- r.Get("/repodata/{filename}", rpm.GetRepositoryFile)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
@@ -581,7 +641,7 @@ func CommonRoutes() *web.Route {
})
})
}, reqPackageAccess(perm.AccessModeRead))
- }, context_service.UserAssignmentWeb(), context.PackageAssignment())
+ }, context.UserAssignmentWeb(), context.PackageAssignment())
return r
}
@@ -600,7 +660,10 @@ func ContainerRoutes() *web.Route {
})
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
- r.Get("/token", container.Authenticate)
+ r.Group("/token", func() {
+ r.Get("", container.Authenticate)
+ r.Post("", container.AuthenticateNotImplemented)
+ })
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.Group("/{image}", func() {
@@ -748,7 +811,7 @@ func ContainerRoutes() *web.Route {
ctx.Status(http.StatusNotFound)
})
- }, container.ReqContainerAccess, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
+ }, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
return r
}
diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go
index 8f1e965c9a3..d01a13d78f6 100644
--- a/routers/api/packages/cargo/cargo.go
+++ b/routers/api/packages/cargo/cargo.go
@@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
cargo_module "code.gitea.io/gitea/modules/packages/cargo"
@@ -20,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages"
cargo_service "code.gitea.io/gitea/services/packages/cargo"
diff --git a/routers/api/packages/chef/auth.go b/routers/api/packages/chef/auth.go
index 3aef8281a4e..a790e9a3631 100644
--- a/routers/api/packages/chef/auth.go
+++ b/routers/api/packages/chef/auth.go
@@ -8,6 +8,7 @@ import (
"crypto"
"crypto/rsa"
"crypto/sha1"
+ "crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
@@ -26,8 +27,6 @@ import (
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth"
-
- "github.com/minio/sha256-simd"
)
const (
diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go
index f1e9ae12d81..720fce0a2a5 100644
--- a/routers/api/packages/chef/chef.go
+++ b/routers/api/packages/chef/chef.go
@@ -15,12 +15,12 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go
index 0551093cd19..346408d2611 100644
--- a/routers/api/packages/composer/composer.go
+++ b/routers/api/packages/composer/composer.go
@@ -14,12 +14,12 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
composer_module "code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages"
diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go
index 4bf13222dc7..c45e085a4d9 100644
--- a/routers/api/packages/conan/conan.go
+++ b/routers/api/packages/conan/conan.go
@@ -15,13 +15,13 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
conan_model "code.gitea.io/gitea/models/packages/conan"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
conan_module "code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/conan/search.go b/routers/api/packages/conan/search.go
index 2bcf9df162b..7370c702cd6 100644
--- a/routers/api/packages/conan/search.go
+++ b/routers/api/packages/conan/search.go
@@ -9,9 +9,9 @@ import (
conan_model "code.gitea.io/gitea/models/packages/conan"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
conan_module "code.gitea.io/gitea/modules/packages/conan"
+ "code.gitea.io/gitea/services/context"
)
// SearchResult contains the found recipe names
diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go
index 0bee7baa96f..30c80fc15e0 100644
--- a/routers/api/packages/conda/conda.go
+++ b/routers/api/packages/conda/conda.go
@@ -12,13 +12,13 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
conda_model "code.gitea.io/gitea/models/packages/conda"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
conda_module "code.gitea.io/gitea/modules/packages/conda"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/dsnet/compress/bzip2"
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go
index 62eec3064cd..e5197661423 100644
--- a/routers/api/packages/container/container.go
+++ b/routers/api/packages/container/container.go
@@ -17,7 +17,6 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -25,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
container_service "code.gitea.io/gitea/services/packages/container"
@@ -114,11 +114,15 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
})
}
-// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost for anonymous access)
+func apiUnauthorizedError(ctx *context.Context) {
+ ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
+ apiErrorDefined(ctx, errUnauthorized)
+}
+
+// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled)
func ReqContainerAccess(ctx *context.Context) {
- if ctx.Doer == nil {
- ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
- apiErrorDefined(ctx, errUnauthorized)
+ if ctx.Doer == nil || (setting.Service.RequireSignInView && ctx.Doer.IsGhost()) {
+ apiUnauthorizedError(ctx)
}
}
@@ -138,10 +142,15 @@ func DetermineSupport(ctx *context.Context) {
}
// Authenticate creates a token for the current user
-// If the current user is anonymous, the ghost user is used
+// If the current user is anonymous, the ghost user is used unless RequireSignInView is enabled.
func Authenticate(ctx *context.Context) {
u := ctx.Doer
if u == nil {
+ if setting.Service.RequireSignInView {
+ apiUnauthorizedError(ctx)
+ return
+ }
+
u = user_model.NewGhostUser()
}
@@ -156,6 +165,17 @@ func Authenticate(ctx *context.Context) {
})
}
+// https://distribution.github.io/distribution/spec/auth/oauth/
+func AuthenticateNotImplemented(ctx *context.Context) {
+ // This optional endpoint can be used to authenticate a client.
+ // It must implement the specification described in:
+ // https://datatracker.ietf.org/doc/html/rfc6749
+ // https://distribution.github.io/distribution/spec/auth/oauth/
+ // Purpose of this stub is to respond with 404 Not Found instead of 405 Method Not Allowed.
+
+ ctx.Status(http.StatusNotFound)
+}
+
// https://docs.docker.com/registry/spec/api/#listing-repositories
func GetRepositoryList(ctx *context.Context) {
n := ctx.FormInt("n")
diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go
index ae43df7c9aa..2cec75294fe 100644
--- a/routers/api/packages/cran/cran.go
+++ b/routers/api/packages/cran/cran.go
@@ -13,11 +13,11 @@ import (
packages_model "code.gitea.io/gitea/models/packages"
cran_model "code.gitea.io/gitea/models/packages/cran"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
cran_module "code.gitea.io/gitea/modules/packages/cran"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go
index 379137e87eb..241de3ac5d9 100644
--- a/routers/api/packages/debian/debian.go
+++ b/routers/api/packages/debian/debian.go
@@ -13,11 +13,11 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
debian_module "code.gitea.io/gitea/modules/packages/debian"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
debian_service "code.gitea.io/gitea/services/packages/debian"
diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index 30854335c09..b65870a8d09 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -10,10 +10,10 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go
index 18e0074ab4d..9eb515d9a1b 100644
--- a/routers/api/packages/goproxy/goproxy.go
+++ b/routers/api/packages/goproxy/goproxy.go
@@ -12,11 +12,11 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
goproxy_module "code.gitea.io/gitea/modules/packages/goproxy"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go
index a8daa69dc3a..e7a346d9ca7 100644
--- a/routers/api/packages/helm/helm.go
+++ b/routers/api/packages/helm/helm.go
@@ -13,7 +13,6 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -21,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"gopkg.in/yaml.v3"
diff --git a/routers/api/packages/helper/helper.go b/routers/api/packages/helper/helper.go
index aadb10376c8..cdb64109ade 100644
--- a/routers/api/packages/helper/helper.go
+++ b/routers/api/packages/helper/helper.go
@@ -10,9 +10,9 @@ import (
"net/url"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// LogAndProcessError logs an error and calls a custom callback with the processed error message.
diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go
index 0b93382b011..27f0578db70 100644
--- a/routers/api/packages/maven/maven.go
+++ b/routers/api/packages/maven/maven.go
@@ -6,6 +6,7 @@ package maven
import (
"crypto/md5"
"crypto/sha1"
+ "crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/xml"
@@ -19,15 +20,13 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
maven_module "code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
-
- "github.com/minio/sha256-simd"
)
const (
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 170edfbe11c..72b43059281 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -17,12 +17,12 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
npm_module "code.gitea.io/gitea/modules/packages/npm"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version"
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index 769c4c18245..a0273aad5ad 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -17,13 +17,13 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
nuget_model "code.gitea.io/gitea/models/packages/nuget"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go
index 1f605c6c9f4..f87df52a29a 100644
--- a/routers/api/packages/pub/pub.go
+++ b/routers/api/packages/pub/pub.go
@@ -14,7 +14,6 @@ import (
"time"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -22,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go
index 5718b1203b4..7824db18235 100644
--- a/routers/api/packages/pypi/pypi.go
+++ b/routers/api/packages/pypi/pypi.go
@@ -12,12 +12,12 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
pypi_module "code.gitea.io/gitea/modules/packages/pypi"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go
index f5d8b67e16d..4de361c2142 100644
--- a/routers/api/packages/rpm/rpm.go
+++ b/routers/api/packages/rpm/rpm.go
@@ -13,13 +13,13 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
rpm_service "code.gitea.io/gitea/services/packages/rpm"
@@ -33,11 +33,18 @@ func apiError(ctx *context.Context, status int, obj any) {
// https://dnf.readthedocs.io/en/latest/conf_ref.html
func GetRepositoryConfig(ctx *context.Context) {
+ group := ctx.Params("group")
+
+ var groupParts []string
+ if group != "" {
+ groupParts = strings.Split(group, "/")
+ }
+
url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
- ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`]
-name=`+ctx.Package.Owner.Name+` - `+setting.AppName+`
-baseurl=`+url+`
+ ctx.PlainText(http.StatusOK, `[gitea-`+strings.Join(append([]string{ctx.Package.Owner.LowerName}, groupParts...), "-")+`]
+name=`+strings.Join(append([]string{ctx.Package.Owner.Name, setting.AppName}, groupParts...), " - ")+`
+baseurl=`+strings.Join(append([]string{url}, groupParts...), "/")+`
enabled=1
gpgcheck=1
gpgkey=`+url+`/repository.key`)
@@ -57,6 +64,30 @@ func GetRepositoryKey(ctx *context.Context) {
})
}
+func CheckRepositoryFileExistence(ctx *context.Context) {
+ pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
+ if err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
+ return
+ }
+
+ pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), ctx.Params("group"))
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.Status(http.StatusNotFound)
+ } else {
+ apiError(ctx, http.StatusInternalServerError, err)
+ }
+ return
+ }
+
+ ctx.SetServeHeaders(&context.ServeHeaderOptions{
+ Filename: pf.Name,
+ LastModified: pf.CreatedUnix.AsLocalTime(),
+ })
+ ctx.Status(http.StatusOK)
+}
+
// Gets a pre-generated repository metadata file
func GetRepositoryFile(ctx *context.Context) {
pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
@@ -69,7 +100,8 @@ func GetRepositoryFile(ctx *context.Context) {
ctx,
pv,
&packages_service.PackageFileInfo{
- Filename: ctx.Params("filename"),
+ Filename: ctx.Params("filename"),
+ CompositeKey: ctx.Params("group"),
},
)
if err != nil {
@@ -121,7 +153,7 @@ func UploadPackageFile(ctx *context.Context) {
apiError(ctx, http.StatusInternalServerError, err)
return
}
-
+ group := ctx.Params("group")
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
ctx,
&packages_service.PackageCreationInfo{
@@ -136,13 +168,16 @@ func UploadPackageFile(ctx *context.Context) {
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
- Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
+ Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
+ CompositeKey: group,
},
Creator: ctx.Doer,
Data: buf,
IsLead: true,
Properties: map[string]string{
- rpm_module.PropertyMetadata: string(fileMetadataRaw),
+ rpm_module.PropertyGroup: group,
+ rpm_module.PropertyArchitecture: pck.FileMetadata.Architecture,
+ rpm_module.PropertyMetadata: string(fileMetadataRaw),
},
},
)
@@ -158,7 +193,7 @@ func UploadPackageFile(ctx *context.Context) {
return
}
- if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil {
+ if err := rpm_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
@@ -179,7 +214,8 @@ func DownloadPackageFile(ctx *context.Context) {
Version: version,
},
&packages_service.PackageFileInfo{
- Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
+ Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
+ CompositeKey: ctx.Params("group"),
},
)
if err != nil {
@@ -195,6 +231,7 @@ func DownloadPackageFile(ctx *context.Context) {
}
func DeletePackageFile(webctx *context.Context) {
+ group := webctx.Params("group")
name := webctx.Params("name")
version := webctx.Params("version")
architecture := webctx.Params("architecture")
@@ -202,7 +239,12 @@ func DeletePackageFile(webctx *context.Context) {
var pd *packages_model.PackageDescriptor
err := db.WithTx(webctx, func(ctx stdctx.Context) error {
- pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version)
+ pv, err := packages_model.GetVersionByNameAndVersion(ctx,
+ webctx.Package.Owner.ID,
+ packages_model.TypeRpm,
+ name,
+ version,
+ )
if err != nil {
return err
}
@@ -211,7 +253,7 @@ func DeletePackageFile(webctx *context.Context) {
ctx,
pv.ID,
fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture),
- packages_model.EmptyFileKey,
+ group,
)
if err != nil {
return err
@@ -251,7 +293,7 @@ func DeletePackageFile(webctx *context.Context) {
notify_service.PackageDelete(webctx, webctx.Doer, pd)
}
- if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil {
+ if err := rpm_service.BuildSpecificRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
apiError(webctx, http.StatusInternalServerError, err)
return
}
diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go
index 01fd4dad669..5d05b6d524d 100644
--- a/routers/api/packages/rubygems/rubygems.go
+++ b/routers/api/packages/rubygems/rubygems.go
@@ -13,11 +13,11 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go
index 427e262d063..1fc8baeaacd 100644
--- a/routers/api/packages/swift/swift.go
+++ b/routers/api/packages/swift/swift.go
@@ -13,7 +13,6 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -21,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version"
@@ -157,7 +157,7 @@ func EnumeratePackageVersions(ctx *context.Context) {
}
type Resource struct {
- Name string `json:"id"`
+ Name string `json:"name"`
Type string `json:"type"`
Checksum string `json:"checksum"`
}
diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go
index af9cd08a629..98a81da368e 100644
--- a/routers/api/packages/vagrant/vagrant.go
+++ b/routers/api/packages/vagrant/vagrant.go
@@ -12,11 +12,11 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
vagrant_module "code.gitea.io/gitea/modules/packages/vagrant"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/packages/helper"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version"
diff --git a/routers/api/v1/activitypub/person.go b/routers/api/v1/activitypub/person.go
index cad5032d103..995a148f0bd 100644
--- a/routers/api/v1/activitypub/person.go
+++ b/routers/api/v1/activitypub/person.go
@@ -9,9 +9,9 @@ import (
"strings"
"code.gitea.io/gitea/modules/activitypub"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub"
"github.com/go-ap/jsonld"
diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go
index 3f60ed7776a..59ebc74b89e 100644
--- a/routers/api/v1/activitypub/reqsignature.go
+++ b/routers/api/v1/activitypub/reqsignature.go
@@ -13,9 +13,9 @@ import (
"net/url"
"code.gitea.io/gitea/modules/activitypub"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting"
+ gitea_context "code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub"
"github.com/go-fed/httpsig"
diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go
index bf030eb222b..a4708fe0326 100644
--- a/routers/api/v1/admin/adopt.go
+++ b/routers/api/v1/admin/adopt.go
@@ -8,9 +8,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/admin/cron.go b/routers/api/v1/admin/cron.go
index cc8c6c9e239..e1ca6048c93 100644
--- a/routers/api/v1/admin/cron.go
+++ b/routers/api/v1/admin/cron.go
@@ -6,11 +6,11 @@ package admin
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/cron"
)
diff --git a/routers/api/v1/admin/email.go b/routers/api/v1/admin/email.go
index 5914215bc2a..ba963e9f69d 100644
--- a/routers/api/v1/admin/email.go
+++ b/routers/api/v1/admin/email.go
@@ -7,9 +7,9 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go
index 8a095a7defa..2217d002a02 100644
--- a/routers/api/v1/admin/hooks.go
+++ b/routers/api/v1/admin/hooks.go
@@ -8,12 +8,12 @@ import (
"net/http"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go
index bf68942a9ca..a5c299bbf01 100644
--- a/routers/api/v1/admin/org.go
+++ b/routers/api/v1/admin/org.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/admin/repo.go b/routers/api/v1/admin/repo.go
index a4895f260be..c119d5390a2 100644
--- a/routers/api/v1/admin/repo.go
+++ b/routers/api/v1/admin/repo.go
@@ -4,10 +4,10 @@
package admin
import (
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/repo"
+ "code.gitea.io/gitea/services/context"
)
// CreateRepo api for creating a repository
diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go
new file mode 100644
index 00000000000..329242d9f6e
--- /dev/null
+++ b/routers/api/v1/admin/runners.go
@@ -0,0 +1,26 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package admin
+
+import (
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
+)
+
+// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
+
+// GetRegistrationToken returns the token to register global runners
+func GetRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken
+ // ---
+ // summary: Get an global actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, 0, 0)
+}
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 09d7c1a9403..64315108b08 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"net/http"
- "strings"
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -16,16 +15,16 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/mailer"
user_service "code.gitea.io/gitea/services/user"
@@ -93,26 +92,32 @@ func CreateUser(ctx *context.APIContext) {
if ctx.Written() {
return
}
- if !password.IsComplexEnough(form.Password) {
- err := errors.New("PasswordComplexity")
- ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
- return
- }
- pwned, err := password.IsPwned(ctx, form.Password)
- if pwned {
- if err != nil {
- log.Error(err.Error())
+
+ if u.LoginType == auth.Plain {
+ if len(form.Password) < setting.MinPasswordLength {
+ err := errors.New("PasswordIsRequired")
+ ctx.Error(http.StatusBadRequest, "PasswordIsRequired", err)
+ return
+ }
+
+ if !password.IsComplexEnough(form.Password) {
+ err := errors.New("PasswordComplexity")
+ ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
+ return
+ }
+
+ if err := password.IsPwned(ctx, form.Password); err != nil {
+ if password.IsErrIsPwnedRequest(err) {
+ log.Error(err.Error())
+ }
+ ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
+ return
}
- ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
- return
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolTrue,
- }
-
- if form.Restricted != nil {
- overwriteDefault.IsRestricted = util.OptionalBoolOf(*form.Restricted)
+ IsActive: optional.Some(true),
+ IsRestricted: optional.FromPtr(form.Restricted),
}
if form.Visibility != "" {
@@ -173,6 +178,8 @@ func EditUser(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/User"
+ // "400":
+ // "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
@@ -180,111 +187,65 @@ func EditUser(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditUserOption)
- parseAuthSource(ctx, ctx.ContextUser, form.SourceID, form.LoginName)
- if ctx.Written() {
+ authOpts := &user_service.UpdateAuthOptions{
+ LoginSource: optional.FromNonDefault(form.SourceID),
+ LoginName: optional.Some(form.LoginName),
+ Password: optional.FromNonDefault(form.Password),
+ MustChangePassword: optional.FromPtr(form.MustChangePassword),
+ ProhibitLogin: optional.FromPtr(form.ProhibitLogin),
+ }
+ if err := user_service.UpdateAuth(ctx, ctx.ContextUser, authOpts); err != nil {
+ switch {
+ case errors.Is(err, password.ErrMinLength):
+ ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
+ case errors.Is(err, password.ErrComplexity):
+ ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
+ case errors.Is(err, password.ErrIsPwned), password.IsErrIsPwnedRequest(err):
+ ctx.Error(http.StatusBadRequest, "PasswordIsPwned", err)
+ default:
+ ctx.Error(http.StatusInternalServerError, "UpdateAuth", err)
+ }
return
}
- if len(form.Password) != 0 {
- if len(form.Password) < setting.MinPasswordLength {
- ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
- return
- }
- if !password.IsComplexEnough(form.Password) {
- err := errors.New("PasswordComplexity")
- ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
- return
- }
- pwned, err := password.IsPwned(ctx, form.Password)
- if pwned {
- if err != nil {
- log.Error(err.Error())
- }
- ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
- return
- }
- if ctx.ContextUser.Salt, err = user_model.GetUserSalt(); err != nil {
- ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
- return
- }
- if err = ctx.ContextUser.SetPassword(form.Password); err != nil {
- ctx.InternalServerError(err)
- return
- }
- }
-
- if form.MustChangePassword != nil {
- ctx.ContextUser.MustChangePassword = *form.MustChangePassword
- }
-
- ctx.ContextUser.LoginName = form.LoginName
-
- if form.FullName != nil {
- ctx.ContextUser.FullName = *form.FullName
- }
- var emailChanged bool
if form.Email != nil {
- email := strings.TrimSpace(*form.Email)
- if len(email) == 0 {
- ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("email is not allowed to be empty string"))
+ if err := user_service.AddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
+ switch {
+ case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
+ ctx.Error(http.StatusBadRequest, "EmailInvalid", err)
+ case user_model.IsErrEmailAlreadyUsed(err):
+ ctx.Error(http.StatusBadRequest, "EmailUsed", err)
+ default:
+ ctx.Error(http.StatusInternalServerError, "AddOrSetPrimaryEmailAddress", err)
+ }
return
}
-
- if err := user_model.ValidateEmail(email); err != nil {
- ctx.InternalServerError(err)
- return
- }
-
- emailChanged = !strings.EqualFold(ctx.ContextUser.Email, email)
- ctx.ContextUser.Email = email
- }
- if form.Website != nil {
- ctx.ContextUser.Website = *form.Website
- }
- if form.Location != nil {
- ctx.ContextUser.Location = *form.Location
- }
- if form.Description != nil {
- ctx.ContextUser.Description = *form.Description
- }
- if form.Active != nil {
- ctx.ContextUser.IsActive = *form.Active
- }
- if len(form.Visibility) != 0 {
- ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
- }
- if form.Admin != nil {
- ctx.ContextUser.IsAdmin = *form.Admin
- }
- if form.AllowGitHook != nil {
- ctx.ContextUser.AllowGitHook = *form.AllowGitHook
- }
- if form.AllowImportLocal != nil {
- ctx.ContextUser.AllowImportLocal = *form.AllowImportLocal
- }
- if form.MaxRepoCreation != nil {
- ctx.ContextUser.MaxRepoCreation = *form.MaxRepoCreation
- }
- if form.AllowCreateOrganization != nil {
- ctx.ContextUser.AllowCreateOrganization = *form.AllowCreateOrganization
- }
- if form.ProhibitLogin != nil {
- ctx.ContextUser.ProhibitLogin = *form.ProhibitLogin
- }
- if form.Restricted != nil {
- ctx.ContextUser.IsRestricted = *form.Restricted
}
- if err := user_model.UpdateUser(ctx, ctx.ContextUser, emailChanged); err != nil {
- if user_model.IsErrEmailAlreadyUsed(err) ||
- user_model.IsErrEmailCharIsNotSupported(err) ||
- user_model.IsErrEmailInvalid(err) {
- ctx.Error(http.StatusUnprocessableEntity, "", err)
+ opts := &user_service.UpdateOptions{
+ FullName: optional.FromPtr(form.FullName),
+ Website: optional.FromPtr(form.Website),
+ Location: optional.FromPtr(form.Location),
+ Description: optional.FromPtr(form.Description),
+ IsActive: optional.FromPtr(form.Active),
+ IsAdmin: optional.FromPtr(form.Admin),
+ Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
+ AllowGitHook: optional.FromPtr(form.AllowGitHook),
+ AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
+ MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
+ AllowCreateOrganization: optional.FromPtr(form.AllowCreateOrganization),
+ IsRestricted: optional.FromPtr(form.Restricted),
+ }
+
+ if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil {
+ if models.IsErrDeleteLastAdminUser(err) {
+ ctx.Error(http.StatusBadRequest, "LastAdmin", err)
} else {
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
}
return
}
+
log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name)
ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer))
@@ -331,7 +292,8 @@ func DeleteUser(ctx *context.APIContext) {
if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil {
if models.IsErrUserOwnRepos(err) ||
models.IsErrUserHasOrgs(err) ||
- models.IsErrUserOwnPackages(err) {
+ models.IsErrUserOwnPackages(err) ||
+ models.IsErrDeleteLastAdminUser(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "DeleteUser", err)
@@ -510,9 +472,6 @@ func RenameUser(ctx *context.APIContext) {
// Check if user name has been changed
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
switch {
- case user_model.IsErrUsernameNotChanged(err):
- // Noop as username is not changed
- ctx.Status(http.StatusNoContent)
case user_model.IsErrUserAlreadyExist(err):
ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
case db.IsErrNameReserved(err):
@@ -528,5 +487,5 @@ func RenameUser(ctx *context.APIContext) {
}
log.Trace("User name changed: %s -> %s", oldName, newName)
- ctx.Status(http.StatusOK)
+ ctx.Status(http.StatusNoContent)
}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 623c798feea..0913571c270 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -6,9 +6,9 @@
//
// This documentation describes the Gitea API.
//
-// Schemes: http, https
+// Schemes: https, http
// BasePath: /api/v1
-// Version: {{AppVer | JSEscape | Safe}}
+// Version: {{AppVer | JSEscape}}
// License: MIT http://opensource.org/licenses/MIT
//
// Consumes:
@@ -35,10 +35,12 @@
// type: apiKey
// name: token
// in: query
+// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
// AccessToken:
// type: apiKey
// name: access_token
// in: query
+// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
// AuthorizationHeaderToken:
// type: apiKey
// name: Authorization
@@ -77,7 +79,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -93,7 +94,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/auth"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
@@ -788,6 +789,31 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
}
}
+func individualPermsChecker(ctx *context.APIContext) {
+ // org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
+ if ctx.ContextUser.IsIndividual() {
+ switch {
+ case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
+ if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
+ ctx.NotFound("Visit Project", nil)
+ return
+ }
+ case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
+ if ctx.Doer == nil {
+ ctx.NotFound("Visit Project", nil)
+ return
+ }
+ }
+ }
+}
+
+// check for and warn against deprecated authentication options
+func checkDeprecatedAuthMethods(ctx *context.APIContext) {
+ if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
+ ctx.Resp.Header().Set("X-Gitea-Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
+ }
+}
+
// Routes registers all v1 APIs routes to web application.
func Routes() *web.Route {
m := web.NewRoute()
@@ -795,9 +821,7 @@ func Routes() *web.Route {
m.Use(securityHeaders())
if setting.CORSConfig.Enabled {
m.Use(cors.Handler(cors.Options{
- // Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
- AllowedOrigins: setting.CORSConfig.AllowDomain,
- // setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
+ AllowedOrigins: setting.CORSConfig.AllowDomain,
AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
@@ -806,6 +830,8 @@ func Routes() *web.Route {
}
m.Use(context.APIContexter())
+ m.Use(checkDeprecatedAuthMethods)
+
// Get user from session if logged in.
m.Use(apiAuth(buildAuthGroup()))
@@ -828,11 +854,11 @@ func Routes() *web.Route {
m.Group("/user/{username}", func() {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
m.Group("/user-id/{user-id}", func() {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
- }, context_service.UserIDAssignmentAPI())
+ }, context.UserIDAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
}
@@ -888,7 +914,7 @@ func Routes() *web.Route {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI(), individualPermsChecker)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope)
@@ -906,7 +932,7 @@ func Routes() *web.Route {
m.Get("/starred", user.GetStarredRepos)
m.Get("/subscriptions", user.GetWatchedRepos)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Users (requires user scope)
@@ -921,11 +947,17 @@ func Routes() *web.Route {
Post(bind(api.CreateEmailOption{}), user.AddEmail).
Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
- // create or update a user's actions secrets
- m.Group("/actions/secrets", func() {
- m.Combo("/{secretname}").
- Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret).
- Delete(user.DeleteSecret)
+ // manage user-level actions features
+ m.Group("/actions", func() {
+ m.Group("/secrets", func() {
+ m.Combo("/{secretname}").
+ Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret).
+ Delete(user.DeleteSecret)
+ })
+
+ m.Group("/runners", func() {
+ m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
+ })
})
m.Get("/followers", user.ListMyFollowers)
@@ -935,7 +967,7 @@ func Routes() *web.Route {
m.Get("", user.CheckMyFollowing)
m.Put("", user.Follow)
m.Delete("", user.Unfollow)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
})
// (admin:public_key scope)
@@ -1025,10 +1057,16 @@ func Routes() *web.Route {
m.Post("/accept", repo.AcceptTransfer)
m.Post("/reject", repo.RejectTransfer)
}, reqToken())
- m.Group("/actions/secrets", func() {
- m.Combo("/{secretname}").
- Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
- Delete(reqToken(), reqOwner(), repo.DeleteSecret)
+ m.Group("/actions", func() {
+ m.Group("/secrets", func() {
+ m.Combo("/{secretname}").
+ Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
+ Delete(reqToken(), reqOwner(), repo.DeleteSecret)
+ })
+
+ m.Group("/runners", func() {
+ m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
+ })
})
m.Group("/hooks/git", func() {
m.Combo("").Get(repo.ListGitHooks)
@@ -1117,9 +1155,9 @@ func Routes() *web.Route {
m.Get("/subscribers", repo.ListSubscribers)
m.Group("/subscription", func() {
m.Get("", user.IsWatching)
- m.Put("", reqToken(), user.Watch)
- m.Delete("", reqToken(), user.Unwatch)
- })
+ m.Put("", user.Watch)
+ m.Delete("", user.Unwatch)
+ }, reqToken())
m.Group("/releases", func() {
m.Combo("").Get(repo.ListReleases).
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
@@ -1142,13 +1180,13 @@ func Routes() *web.Route {
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
})
}, reqRepoReader(unit.TypeReleases))
- m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), repo.MirrorSync)
- m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), repo.PushMirrorSync)
+ m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.MirrorSync)
+ m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), mustNotBeArchived, repo.PushMirrorSync)
m.Group("/push_mirrors", func() {
m.Combo("").Get(repo.ListPushMirrors).
- Post(bind(api.CreatePushMirrorOption{}), repo.AddPushMirror)
+ Post(mustNotBeArchived, bind(api.CreatePushMirrorOption{}), repo.AddPushMirror)
m.Combo("/{name}").
- Delete(repo.DeletePushMirrorByRemoteName).
+ Delete(mustNotBeArchived, repo.DeletePushMirrorByRemoteName).
Get(repo.GetPushMirrorByName)
}, reqAdmin(), reqToken())
@@ -1186,6 +1224,7 @@ func Routes() *web.Route {
Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
})
+ m.Get("/{base}/*", repo.GetPullRequestByBaseHead)
}, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
m.Group("/statuses", func() {
m.Combo("/{sha}").Get(repo.GetCommitStatuses).
@@ -1196,6 +1235,7 @@ func Routes() *web.Route {
m.Group("/{ref}", func() {
m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef)
+ m.Get("/pull", repo.GetCommitPullRequest)
}, context.ReferencesGitRepo())
}, reqRepoReader(unit.TypeCode))
m.Group("/git", func() {
@@ -1374,14 +1414,14 @@ func Routes() *web.Route {
m.Get("/files", reqToken(), packages.ListPackageFiles)
})
m.Get("/", reqToken(), packages.ListPackages)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
// Organizations
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI())
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
m.Group("/orgs/{org}", func() {
@@ -1395,11 +1435,17 @@ func Routes() *web.Route {
m.Combo("/{username}").Get(reqToken(), org.IsMember).
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
})
- m.Group("/actions/secrets", func() {
- m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
- m.Combo("/{secretname}").
- Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
- Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
+ m.Group("/actions", func() {
+ m.Group("/secrets", func() {
+ m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
+ m.Combo("/{secretname}").
+ Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
+ Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
+ })
+
+ m.Group("/runners", func() {
+ m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
+ })
})
m.Group("/public_members", func() {
m.Get("", org.ListPublicMembers)
@@ -1473,7 +1519,7 @@ func Routes() *web.Route {
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
- }, context_service.UserAssignmentAPI())
+ }, context.UserAssignmentAPI())
})
m.Group("/emails", func() {
m.Get("", admin.GetAllEmails)
@@ -1491,6 +1537,9 @@ func Routes() *web.Route {
Patch(bind(api.EditHookOption{}), admin.EditHook).
Delete(admin.DeleteHook)
})
+ m.Group("/runners", func() {
+ m.Get("/registration-token", admin.GetRegistrationToken)
+ })
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
m.Group("/topics", func() {
diff --git a/routers/api/v1/misc/gitignore.go b/routers/api/v1/misc/gitignore.go
index 7c7fe4b125f..dffd771752e 100644
--- a/routers/api/v1/misc/gitignore.go
+++ b/routers/api/v1/misc/gitignore.go
@@ -6,11 +6,11 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/options"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// Shows a list of all Gitignore templates
diff --git a/routers/api/v1/misc/label_templates.go b/routers/api/v1/misc/label_templates.go
index 0e0ca39fc5d..cc11f376262 100644
--- a/routers/api/v1/misc/label_templates.go
+++ b/routers/api/v1/misc/label_templates.go
@@ -6,9 +6,9 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/misc/licenses.go b/routers/api/v1/misc/licenses.go
index 65f63468cfd..2a980f5084d 100644
--- a/routers/api/v1/misc/licenses.go
+++ b/routers/api/v1/misc/licenses.go
@@ -8,12 +8,12 @@ import (
"net/http"
"net/url"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/options"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// Returns a list of all License templates
diff --git a/routers/api/v1/misc/markup.go b/routers/api/v1/misc/markup.go
index 7b24b353b63..9699c79368a 100644
--- a/routers/api/v1/misc/markup.go
+++ b/routers/api/v1/misc/markup.go
@@ -6,12 +6,12 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
)
// Markup render markup document to HTML
diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go
index ec8f8f47b70..f499501c2fc 100644
--- a/routers/api/v1/misc/markup_test.go
+++ b/routers/api/v1/misc/markup_test.go
@@ -10,11 +10,11 @@ import (
"strings"
"testing"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go
index f0d8d80dd4e..3bd80de5c18 100644
--- a/routers/api/v1/misc/nodeinfo.go
+++ b/routers/api/v1/misc/nodeinfo.go
@@ -9,9 +9,9 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
@@ -29,10 +29,9 @@ func NodeInfo(ctx *context.APIContext) {
nodeInfoUsage := structs.NodeInfoUsage{}
if setting.Federation.ShareUserStatistics {
- cached := false
- if setting.CacheService.Enabled {
- nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
- }
+ var cached bool
+ nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
+
if !cached {
usersTotal := int(user_model.CountUsers(ctx, nil))
now := time.Now()
@@ -53,11 +52,10 @@ func NodeInfo(ctx *context.APIContext) {
LocalPosts: int(allIssues),
LocalComments: int(allComments),
}
- if setting.CacheService.Enabled {
- if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
- ctx.InternalServerError(err)
- return
- }
+
+ if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
+ ctx.InternalServerError(err)
+ return
}
}
}
diff --git a/routers/api/v1/misc/signing.go b/routers/api/v1/misc/signing.go
index 2ca9813e15c..24a46c1e704 100644
--- a/routers/api/v1/misc/signing.go
+++ b/routers/api/v1/misc/signing.go
@@ -7,8 +7,8 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
)
// SigningKey returns the public key of the default signing key if it exists
diff --git a/routers/api/v1/misc/version.go b/routers/api/v1/misc/version.go
index 83fa35219ab..e3b43a0e6b6 100644
--- a/routers/api/v1/misc/version.go
+++ b/routers/api/v1/misc/version.go
@@ -6,9 +6,9 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
// Version shows the version of the Gitea server
diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go
index c87da9399f0..46b3c7f5e75 100644
--- a/routers/api/v1/notify/notifications.go
+++ b/routers/api/v1/notify/notifications.go
@@ -9,9 +9,9 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
)
// NewAvailable check if unread notifications exist
diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go
index 55ca6ad1fd5..8d97e8a3f84 100644
--- a/routers/api/v1/notify/repo.go
+++ b/routers/api/v1/notify/repo.go
@@ -10,9 +10,9 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go
index 919e52952dc..8e12d359cb3 100644
--- a/routers/api/v1/notify/threads.go
+++ b/routers/api/v1/notify/threads.go
@@ -10,7 +10,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go
index 4abdfb2e925..879f484ccee 100644
--- a/routers/api/v1/notify/user.go
+++ b/routers/api/v1/notify/user.go
@@ -9,8 +9,8 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/org/avatar.go b/routers/api/v1/org/avatar.go
index 7b621a50c31..e34c68dfc9d 100644
--- a/routers/api/v1/org/avatar.go
+++ b/routers/api/v1/org/avatar.go
@@ -7,9 +7,9 @@ import (
"encoding/base64"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go
index 3c3f058b5dd..c1dc0519ea1 100644
--- a/routers/api/v1/org/hook.go
+++ b/routers/api/v1/org/hook.go
@@ -6,10 +6,10 @@ package org
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
index 5a03059deda..b5ec54ccf4e 100644
--- a/routers/api/v1/org/label.go
+++ b/routers/api/v1/org/label.go
@@ -9,11 +9,11 @@ import (
"strings"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go
index 422b7cecfee..fb66d4c3f5b 100644
--- a/routers/api/v1/org/member.go
+++ b/routers/api/v1/org/member.go
@@ -9,11 +9,11 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/organization"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index d5fac1e5b8c..e848d951810 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -12,13 +12,15 @@ import (
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/org"
+ user_service "code.gitea.io/gitea/services/user"
)
func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
@@ -337,28 +339,30 @@ func Edit(ctx *context.APIContext) {
// "$ref": "#/responses/Organization"
// "404":
// "$ref": "#/responses/notFound"
+
form := web.GetForm(ctx).(*api.EditOrgOption)
- org := ctx.Org.Organization
- org.FullName = form.FullName
- org.Email = form.Email
- org.Description = form.Description
- org.Website = form.Website
- org.Location = form.Location
- if form.Visibility != "" {
- org.Visibility = api.VisibilityModes[form.Visibility]
+
+ if form.Email != "" {
+ if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Org.Organization.AsUser(), form.Email); err != nil {
+ ctx.Error(http.StatusInternalServerError, "ReplacePrimaryEmailAddress", err)
+ return
+ }
}
- if form.RepoAdminChangeTeamAccess != nil {
- org.RepoAdminChangeTeamAccess = *form.RepoAdminChangeTeamAccess
+
+ opts := &user_service.UpdateOptions{
+ FullName: optional.Some(form.FullName),
+ Description: optional.Some(form.Description),
+ Website: optional.Some(form.Website),
+ Location: optional.Some(form.Location),
+ Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
+ RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
}
- if err := user_model.UpdateUserCols(ctx, org.AsUser(),
- "full_name", "description", "website", "location",
- "visibility", "repo_admin_change_team_access",
- ); err != nil {
- ctx.Error(http.StatusInternalServerError, "EditOrganization", err)
+ if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
+ ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
return
}
- ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, org))
+ ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, ctx.Org.Organization))
}
// Delete an organization
diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go
new file mode 100644
index 00000000000..2a52bd8778f
--- /dev/null
+++ b/routers/api/v1/org/runners.go
@@ -0,0 +1,31 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
+)
+
+// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
+
+// GetRegistrationToken returns the token to register org runners
+func GetRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
+ // ---
+ // summary: Get an organization's actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
+}
diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/secrets.go
similarity index 99%
rename from routers/api/v1/org/action.go
rename to routers/api/v1/org/secrets.go
index ddc74d865b1..abb6bb26c43 100644
--- a/routers/api/v1/org/action.go
+++ b/routers/api/v1/org/secrets.go
@@ -9,11 +9,11 @@ import (
"code.gitea.io/gitea/models/db"
secret_model "code.gitea.io/gitea/models/secret"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index f129c662306..b62a386fd77 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -15,12 +15,12 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go
index a79ba315be2..3be31b13ae1 100644
--- a/routers/api/v1/packages/package.go
+++ b/routers/api/v1/packages/package.go
@@ -7,10 +7,10 @@ import (
"net/http"
"code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages"
)
diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go
index 039cdadac9c..e0af276c715 100644
--- a/routers/api/v1/repo/action.go
+++ b/routers/api/v1/repo/action.go
@@ -7,10 +7,10 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/api/v1/repo/avatar.go b/routers/api/v1/repo/avatar.go
index 1b661955f05..698337ffd2f 100644
--- a/routers/api/v1/repo/avatar.go
+++ b/routers/api/v1/repo/avatar.go
@@ -7,9 +7,9 @@ import (
"encoding/base64"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/repo/blob.go b/routers/api/v1/repo/blob.go
index 26605bba03c..3b116666ead 100644
--- a/routers/api/v1/repo/blob.go
+++ b/routers/api/v1/repo/blob.go
@@ -6,7 +6,7 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
files_service "code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index f2386a607e6..5e6b6a86586 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -10,16 +10,18 @@ import (
"net/http"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
@@ -137,9 +139,9 @@ func DeleteBranch(ctx *context.APIContext) {
}
// check whether branches of this repository has been synced
- totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{
+ totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
@@ -251,12 +253,11 @@ func CreateBranch(ctx *context.APIContext) {
}
}
- err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
+ err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
if err != nil {
if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
- }
- if models.IsErrTagAlreadyExists(err) {
+ } else if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "", "The branch already exists.")
@@ -339,10 +340,10 @@ func ListBranches(ctx *context.APIContext) {
branchOpts := git_model.FindBranchOptions{
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
}
var err error
- totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
+ totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
@@ -361,7 +362,7 @@ func ListBranches(ctx *context.APIContext) {
return
}
- branches, err := git_model.FindBranches(ctx, branchOpts)
+ branches, err := db.Find[git_model.Branch](ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
@@ -615,6 +616,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
BlockOnRejectedReviews: form.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
DismissStaleApprovals: form.DismissStaleApprovals,
+ IgnoreStaleApprovals: form.IgnoreStaleApprovals,
RequireSignedCommits: form.RequireSignedCommits,
ProtectedFilePatterns: form.ProtectedFilePatterns,
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
@@ -642,7 +644,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
} else {
if !isPlainRule {
if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath())
+ ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
@@ -786,6 +788,10 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
}
+ if form.IgnoreStaleApprovals != nil {
+ protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
+ }
+
if form.RequireSignedCommits != nil {
protectBranch.RequireSignedCommits = *form.RequireSignedCommits
}
@@ -915,7 +921,7 @@ func EditBranchProtection(ctx *context.APIContext) {
} else {
if !isPlainRule {
if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath())
+ ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index 2538bcdbc62..7d48d715169 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -8,15 +8,16 @@ import (
"errors"
"net/http"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
)
@@ -53,7 +54,9 @@ func ListCollaborators(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- count, err := repo_model.CountCollaborators(ctx, ctx.Repo.Repository.ID)
+ count, err := db.Count[repo_model.Collaboration](ctx, repo_model.FindCollaborationOptions{
+ RepoID: ctx.Repo.Repository.ID,
+ })
if err != nil {
ctx.InternalServerError(err)
return
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index 20d4405d6d4..d06a3b4e49a 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -10,12 +10,13 @@ import (
"net/http"
"strconv"
+ issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@@ -205,7 +206,6 @@ func GetAllCommits(ctx *context.APIContext) {
Not: not,
Revision: []string{baseCommit.ID.String()},
})
-
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err)
return
@@ -245,7 +245,6 @@ func GetAllCommits(ctx *context.APIContext) {
Not: not,
Page: listOptions.Page,
})
-
if err != nil {
ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err)
return
@@ -325,3 +324,53 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
return
}
}
+
+// GetCommitPullRequest returns the pull request of the commit
+func GetCommitPullRequest(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest
+ // ---
+ // summary: Get the pull request of the commit
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // - name: sha
+ // in: path
+ // description: SHA of the commit to get
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/PullRequest"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.Params(":sha"))
+ if err != nil {
+ if issues_model.IsErrPullRequestNotExist(err) {
+ ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+ }
+ return
+ }
+
+ if err = pr.LoadBaseRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ return
+ }
+ if err = pr.LoadHeadRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
+}
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 94e634461c2..4895f7b1b3f 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -19,8 +19,8 @@ import (
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
@@ -29,6 +29,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
archiver_service "code.gitea.io/gitea/services/repository/archiver"
files_service "code.gitea.io/gitea/services/repository/files"
)
@@ -279,9 +280,8 @@ func GetArchive(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- repoPath := repo_model.RepoPath(ctx.Params(":username"), ctx.Params(":reponame"))
if ctx.Repo.GitRepo == nil {
- gitRepo, err := git.OpenRepository(ctx, repoPath)
+ gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
@@ -408,7 +408,7 @@ func canReadFiles(r *context.Repository) bool {
return r.Permission.CanRead(unit.TypeCode)
}
-func base64Reader(s string) (io.Reader, error) {
+func base64Reader(s string) (io.ReadSeeker, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
@@ -655,6 +655,7 @@ func UpdateFile(ctx *context.APIContext) {
apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
+ return
}
if apiOpts.BranchName == "" {
@@ -762,13 +763,13 @@ func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.Ch
}
message := ""
if len(createFiles) != 0 {
- message += ctx.Tr("repo.editor.add", strings.Join(createFiles, ", ")+"\n")
+ message += ctx.Locale.TrString("repo.editor.add", strings.Join(createFiles, ", ")+"\n")
}
if len(updateFiles) != 0 {
- message += ctx.Tr("repo.editor.update", strings.Join(updateFiles, ", ")+"\n")
+ message += ctx.Locale.TrString("repo.editor.update", strings.Join(updateFiles, ", ")+"\n")
}
if len(deleteFiles) != 0 {
- message += ctx.Tr("repo.editor.delete", strings.Join(deleteFiles, ", "))
+ message += ctx.Locale.TrString("repo.editor.delete", strings.Join(deleteFiles, ", "))
}
return strings.Trim(message, "\n")
}
diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go
index 69433bf4cc4..212cc7a93b5 100644
--- a/routers/api/v1/repo/fork.go
+++ b/routers/api/v1/repo/fork.go
@@ -14,11 +14,11 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/api/v1/repo/git_hook.go b/routers/api/v1/repo/git_hook.go
index 7e471e263b6..26ae84d08d3 100644
--- a/routers/api/v1/repo/git_hook.go
+++ b/routers/api/v1/repo/git_hook.go
@@ -6,10 +6,10 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/git_ref.go b/routers/api/v1/repo/git_ref.go
index 34d2dcfcc81..0fa58425b85 100644
--- a/routers/api/v1/repo/git_ref.go
+++ b/routers/api/v1/repo/git_ref.go
@@ -7,10 +7,10 @@ import (
"net/http"
"net/url"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
)
// GetGitAllRefs get ref or an list all the refs of a repository
diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go
index 8859e3ae236..ffd2313591b 100644
--- a/routers/api/v1/repo/hook.go
+++ b/routers/api/v1/repo/hook.go
@@ -11,13 +11,13 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/repo/hook_test.go b/routers/api/v1/repo/hook_test.go
index 94a71e20ad7..37cf61c1edf 100644
--- a/routers/api/v1/repo/hook_test.go
+++ b/routers/api/v1/repo/hook_test.go
@@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/contexttest"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 0f76a4b4ff2..efc1a08a050 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -18,7 +18,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -26,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
notify_service "code.gitea.io/gitea/services/notify"
diff --git a/routers/api/v1/repo/issue_attachment.go b/routers/api/v1/repo/issue_attachment.go
index 11d19b21ff5..d62e23aa022 100644
--- a/routers/api/v1/repo/issue_attachment.go
+++ b/routers/api/v1/repo/issue_attachment.go
@@ -8,12 +8,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 4db2c68a798..763419b7a25 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -14,11 +14,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
@@ -395,7 +395,7 @@ func CreateIssueComment(ctx *context.APIContext) {
}
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
- ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
+ ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Locale.TrString("repo.issues.comment_on_locked")))
return
}
diff --git a/routers/api/v1/repo/issue_comment_attachment.go b/routers/api/v1/repo/issue_comment_attachment.go
index 21e2f4dabd6..e7436db7982 100644
--- a/routers/api/v1/repo/issue_comment_attachment.go
+++ b/routers/api/v1/repo/issue_comment_attachment.go
@@ -8,12 +8,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go
index 0e3bcd93ef4..a42920d4fd3 100644
--- a/routers/api/v1/repo/issue_dependency.go
+++ b/routers/api/v1/repo/issue_dependency.go
@@ -11,10 +11,10 @@ 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"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@@ -102,23 +102,24 @@ func GetIssueDependencies(ctx *context.APIContext) {
return
}
- var lastRepoID int64
- var lastPerm access_model.Permission
+ repoPerms := make(map[int64]access_model.Permission)
+ repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
for _, blocker := range blockersInfo {
// Get the permissions for this repository
- perm := lastPerm
- if lastRepoID != blocker.Repository.ID {
- if blocker.Repository.ID == ctx.Repo.Repository.ID {
- perm = ctx.Repo.Permission
- } else {
- var err error
- perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
- }
+ // If the repo ID exists in the map, return the exist permissions
+ // else get the permission and add it to the map
+ var perm access_model.Permission
+ existPerm, ok := repoPerms[blocker.RepoID]
+ if ok {
+ perm = existPerm
+ } else {
+ var err error
+ perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return
}
- lastRepoID = blocker.Repository.ID
+ repoPerms[blocker.RepoID] = perm
}
// check permission
@@ -345,29 +346,31 @@ func GetIssueBlocks(ctx *context.APIContext) {
return
}
- var lastRepoID int64
- var lastPerm access_model.Permission
-
var issues []*issues_model.Issue
+
+ repoPerms := make(map[int64]access_model.Permission)
+ repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
+
for i, depMeta := range deps {
if i < skip || i >= max {
continue
}
// Get the permissions for this repository
- perm := lastPerm
- if lastRepoID != depMeta.Repository.ID {
- if depMeta.Repository.ID == ctx.Repo.Repository.ID {
- perm = ctx.Repo.Permission
- } else {
- var err error
- perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
- }
+ // If the repo ID exists in the map, return the exist permissions
+ // else get the permission and add it to the map
+ var perm access_model.Permission
+ existPerm, ok := repoPerms[depMeta.RepoID]
+ if ok {
+ perm = existPerm
+ } else {
+ var err error
+ perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return
}
- lastRepoID = depMeta.Repository.ID
+ repoPerms[depMeta.RepoID] = perm
}
if !perm.CanReadIssuesOrPulls(depMeta.Issue.IsPull) {
diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go
index c2f530956e9..7d9f85d2aa2 100644
--- a/routers/api/v1/repo/issue_label.go
+++ b/routers/api/v1/repo/issue_label.go
@@ -8,9 +8,9 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/api/v1/repo/issue_pin.go b/routers/api/v1/repo/issue_pin.go
index 61f88de34eb..ff1135862b3 100644
--- a/routers/api/v1/repo/issue_pin.go
+++ b/routers/api/v1/repo/issue_pin.go
@@ -7,8 +7,8 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go
index c886bd71b76..799c6878126 100644
--- a/routers/api/v1/repo/issue_reaction.go
+++ b/routers/api/v1/repo/issue_reaction.go
@@ -8,10 +8,10 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_stopwatch.go b/routers/api/v1/repo/issue_stopwatch.go
index 52bf8b5c7b8..d9054e8f775 100644
--- a/routers/api/v1/repo/issue_stopwatch.go
+++ b/routers/api/v1/repo/issue_stopwatch.go
@@ -8,8 +8,8 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go
index ece880c03e1..a535172462e 100644
--- a/routers/api/v1/repo/issue_subscription.go
+++ b/routers/api/v1/repo/issue_subscription.go
@@ -9,9 +9,9 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go
index cf03e72aa0c..c6405158819 100644
--- a/routers/api/v1/repo/issue_tracked_time.go
+++ b/routers/api/v1/repo/issue_tracked_time.go
@@ -12,10 +12,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go
index af48c40885d..88444a26250 100644
--- a/routers/api/v1/repo/key.go
+++ b/routers/api/v1/repo/key.go
@@ -15,12 +15,12 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go
index 420d3ab5b49..b6eb51fd20a 100644
--- a/routers/api/v1/repo/label.go
+++ b/routers/api/v1/repo/label.go
@@ -9,11 +9,11 @@ import (
"strconv"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/language.go b/routers/api/v1/repo/language.go
index 12f1761ad08..f1d5bbe45fe 100644
--- a/routers/api/v1/repo/language.go
+++ b/routers/api/v1/repo/language.go
@@ -9,8 +9,8 @@ import (
"strconv"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
type languageResponse []*repo_model.LanguageStat
diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go
index 839fbfe8a1a..2caaa130e8d 100644
--- a/routers/api/v1/repo/migrate.go
+++ b/routers/api/v1/repo/migrate.go
@@ -17,7 +17,6 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
@@ -26,6 +25,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go
index da470dfa74c..d4c828fe8bf 100644
--- a/routers/api/v1/repo/milestone.go
+++ b/routers/api/v1/repo/milestone.go
@@ -9,12 +9,14 @@ import (
"strconv"
"time"
+ "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@@ -58,14 +60,21 @@ func ListMilestones(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- milestones, total, err := issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
+ state := api.StateType(ctx.FormString("state"))
+ var isClosed util.OptionalBool
+ switch state {
+ case api.StateClosed, api.StateOpen:
+ isClosed = util.OptionalBoolOf(state == api.StateClosed)
+ }
+
+ milestones, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
ListOptions: utils.GetListOptions(ctx),
RepoID: ctx.Repo.Repository.ID,
- State: api.StateType(ctx.FormString("state")),
+ IsClosed: isClosed,
Name: ctx.FormString("name"),
})
if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetMilestones", err)
+ ctx.Error(http.StatusInternalServerError, "db.FindAndCount[issues_model.Milestone]", err)
return
}
diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go
index 72ddc758dce..864644e1efc 100644
--- a/routers/api/v1/repo/mirror.go
+++ b/routers/api/v1/repo/mirror.go
@@ -13,12 +13,12 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
@@ -227,11 +227,18 @@ func GetPushMirrorByName(ctx *context.APIContext) {
mirrorName := ctx.Params(":name")
// Get push mirror of a specific repo by remoteName
- pushMirror, err := repo_model.GetPushMirror(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: mirrorName})
+ pushMirror, exist, err := db.Get[repo_model.PushMirror](ctx, repo_model.PushMirrorOptions{
+ RepoID: ctx.Repo.Repository.ID,
+ RemoteName: mirrorName,
+ }.ToConds())
if err != nil {
- ctx.Error(http.StatusNotFound, "GetPushMirrors", err)
+ ctx.Error(http.StatusInternalServerError, "GetPushMirrors", err)
+ return
+ } else if !exist {
+ ctx.Error(http.StatusNotFound, "GetPushMirrors", nil)
return
}
+
m, err := convert.ToPushMirror(ctx, pushMirror)
if err != nil {
ctx.ServerError("GetPushMirrorByRemoteName", err)
@@ -368,7 +375,7 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
RemoteAddress: remoteAddress,
}
- if err = repo_model.InsertPushMirror(ctx, pushMirror); err != nil {
+ if err = db.Insert(ctx, pushMirror); err != nil {
ctx.ServerError("InsertPushMirror", err)
return
}
diff --git a/routers/api/v1/repo/notes.go b/routers/api/v1/repo/notes.go
index 0b259703dea..a4a1d4eab7c 100644
--- a/routers/api/v1/repo/notes.go
+++ b/routers/api/v1/repo/notes.go
@@ -7,9 +7,9 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
@@ -66,7 +66,7 @@ func getNote(ctx *context.APIContext, identifier string) {
return
}
- commitSHA, err := ctx.Repo.GitRepo.ConvertToSHA1(identifier)
+ commitID, err := ctx.Repo.GitRepo.ConvertToGitID(identifier)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(err)
@@ -77,7 +77,7 @@ func getNote(ctx *context.APIContext, identifier string) {
}
var note git.Note
- if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitSHA.String(), ¬e); err != nil {
+ if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitID.String(), ¬e); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(identifier)
return
diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go
index 9b5635d245a..0e0601b7d93 100644
--- a/routers/api/v1/repo/patch.go
+++ b/routers/api/v1/repo/patch.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index d6b9dddd9d7..8f9848f71d1 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -21,8 +21,8 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/automerge"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/gitdiff"
@@ -186,6 +187,91 @@ func GetPullRequest(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
}
+// GetPullRequest returns a single PR based on index
+func GetPullRequestByBaseHead(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/pulls/{base}/{head} repository repoGetPullRequestByBaseHead
+ // ---
+ // summary: Get a pull request by base and head
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // - name: base
+ // in: path
+ // description: base of the pull request to get
+ // type: string
+ // required: true
+ // - name: head
+ // in: path
+ // description: head of the pull request to get
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/PullRequest"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ var headRepoID int64
+ var headBranch string
+ head := ctx.Params("*")
+ if strings.Contains(head, ":") {
+ split := strings.SplitN(head, ":", 2)
+ headBranch = split[1]
+ var owner, name string
+ if strings.Contains(split[0], "/") {
+ split = strings.Split(split[0], "/")
+ owner = split[0]
+ name = split[1]
+ } else {
+ owner = split[0]
+ name = ctx.Repo.Repository.Name
+ }
+ repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name)
+ if err != nil {
+ if repo_model.IsErrRepoNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetRepositoryByOwnerName", err)
+ }
+ return
+ }
+ headRepoID = repo.ID
+ } else {
+ headRepoID = ctx.Repo.Repository.ID
+ headBranch = head
+ }
+
+ pr, err := issues_model.GetPullRequestByBaseHeadInfo(ctx, ctx.Repo.Repository.ID, headRepoID, ctx.Params(":base"), headBranch)
+ if err != nil {
+ if issues_model.IsErrPullRequestNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetPullRequestByBaseHeadInfo", err)
+ }
+ return
+ }
+
+ if err = pr.LoadBaseRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+ return
+ }
+ if err = pr.LoadHeadRepo(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
+}
+
// DownloadPullDiffOrPatch render a pull's raw diff or patch
func DownloadPullDiffOrPatch(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.{diffType} repository repoDownloadPullDiffOrPatch
@@ -906,13 +992,17 @@ func MergePullRequest(ctx *context.APIContext) {
if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
headRepo = ctx.Repo.GitRepo
} else {
- headRepo, err = git.OpenRepository(ctx, pr.HeadRepo.RepoPath())
+ headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
if err != nil {
- ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
+ ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
return
}
defer headRepo.Close()
}
+ if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
+ ctx.Error(http.StatusInternalServerError, "RetargetChildrenOnMerge", err)
+ return
+ }
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
switch {
case git.IsErrBranchNotExist(err):
@@ -1000,7 +1090,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo
} else {
- headGitRepo, err = git.OpenRepository(ctx, repo_model.RepoPath(headUser.Name, headRepo.Name))
+ headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return nil, nil, nil, nil, "", ""
@@ -1304,7 +1394,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
}
var prInfo *git.CompareInfo
- baseGitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.BaseRepo.RepoPath())
+ baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
if err != nil {
ctx.ServerError("OpenRepository", err)
return
@@ -1329,17 +1419,16 @@ func GetPullRequestCommits(ctx *context.APIContext) {
userCache := make(map[string]*user_model.User)
- start, end := listOptions.GetStartEnd()
+ start, limit := listOptions.GetSkipTake()
- if end > totalNumberOfCommits {
- end = totalNumberOfCommits
- }
+ limit = min(limit, totalNumberOfCommits-start)
+ limit = max(limit, 0)
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
files := ctx.FormString("files") == "" || ctx.FormBool("files")
- apiCommits := make([]*api.Commit, 0, end-start)
- for i := start; i < end; i++ {
+ apiCommits := make([]*api.Commit, 0, limit)
+ for i := start; i < start+limit; i++ {
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, commits[i], userCache,
convert.ToCommitOptions{
Stat: true,
@@ -1477,19 +1566,14 @@ func GetPullRequestFiles(ctx *context.APIContext) {
totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
- start, end := listOptions.GetStartEnd()
+ start, limit := listOptions.GetSkipTake()
- if end > totalNumberOfFiles {
- end = totalNumberOfFiles
- }
+ limit = min(limit, totalNumberOfFiles-start)
- lenFiles := end - start
- if lenFiles < 0 {
- lenFiles = 0
- }
+ limit = max(limit, 0)
- apiFiles := make([]*api.ChangedFile, 0, lenFiles)
- for i := start; i < end; i++ {
+ apiFiles := make([]*api.ChangedFile, 0, limit)
+ for i := start; i < start+limit; i++ {
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
}
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index 7b9445be4c0..5128102e61b 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -12,11 +12,11 @@ import (
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
@@ -329,8 +329,7 @@ func CreatePullReview(ctx *context.APIContext) {
// if CommitID is empty, set it as lastCommitID
if opts.CommitID == "" {
-
- gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.Issue.Repo.RepoPath())
+ gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.Issue.Repo)
if err != nil {
ctx.Error(http.StatusInternalServerError, "git.OpenRepository", err)
return
@@ -363,6 +362,7 @@ func CreatePullReview(ctx *context.APIContext) {
true, // pending review
0, // no reply
opts.CommitID,
+ nil,
); err != nil {
ctx.Error(http.StatusInternalServerError, "CreateCodeComment", err)
return
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index 6c70bffca33..a47fc1cc598 100644
--- a/routers/api/v1/repo/release.go
+++ b/routers/api/v1/repo/release.go
@@ -7,13 +7,14 @@ import (
"net/http"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
release_service "code.gitea.io/gitea/services/release"
)
@@ -133,11 +134,6 @@ func ListReleases(ctx *context.APIContext) {
// in: query
// description: filter (exclude / include) pre-releases
// type: boolean
- // - name: per_page
- // in: query
- // description: page size of results, deprecated - use limit
- // type: integer
- // deprecated: true
// - name: page
// in: query
// description: page number of results to return (1-based)
@@ -152,9 +148,6 @@ func ListReleases(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
- if listOptions.PageSize == 0 && ctx.FormInt("per_page") != 0 {
- listOptions.PageSize = ctx.FormInt("per_page")
- }
opts := repo_model.FindReleasesOptions{
ListOptions: listOptions,
@@ -162,9 +155,10 @@ func ListReleases(ctx *context.APIContext) {
IncludeTags: false,
IsDraft: ctx.FormOptionalBool("draft"),
IsPreRelease: ctx.FormOptionalBool("pre-release"),
+ RepoID: ctx.Repo.Repository.ID,
}
- releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
+ releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
return
@@ -178,7 +172,7 @@ func ListReleases(ctx *context.APIContext) {
rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)
}
- filteredCount, err := repo_model.CountReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
+ filteredCount, err := db.Count[repo_model.Release](ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go
index c36bf12e6d3..a29bce66a4e 100644
--- a/routers/api/v1/repo/release_attachment.go
+++ b/routers/api/v1/repo/release_attachment.go
@@ -7,13 +7,13 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go
index 9f2098df066..fec91164a29 100644
--- a/routers/api/v1/repo/release_tags.go
+++ b/routers/api/v1/repo/release_tags.go
@@ -8,7 +8,7 @@ import (
"code.gitea.io/gitea/models"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
releaseservice "code.gitea.io/gitea/services/release"
)
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 3ce39069bb7..00a291500c1 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -8,6 +8,7 @@ import (
"fmt"
"net/http"
"slices"
+ "strconv"
"strings"
"time"
@@ -19,8 +20,8 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
@@ -30,6 +31,7 @@ import (
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/issue"
repo_service "code.gitea.io/gitea/services/repository"
@@ -242,18 +244,19 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
}
repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_service.CreateRepoOptions{
- Name: opt.Name,
- Description: opt.Description,
- IssueLabels: opt.IssueLabels,
- Gitignores: opt.Gitignores,
- License: opt.License,
- Readme: opt.Readme,
- IsPrivate: opt.Private,
- AutoInit: opt.AutoInit,
- DefaultBranch: opt.DefaultBranch,
- TrustModel: repo_model.ToTrustModel(opt.TrustModel),
- IsTemplate: opt.Template,
- SizeLimit: opt.SizeLimit,
+ Name: opt.Name,
+ Description: opt.Description,
+ IssueLabels: opt.IssueLabels,
+ Gitignores: opt.Gitignores,
+ License: opt.License,
+ Readme: opt.Readme,
+ IsPrivate: opt.Private,
+ AutoInit: opt.AutoInit,
+ DefaultBranch: opt.DefaultBranch,
+ TrustModel: repo_model.ToTrustModel(opt.TrustModel),
+ IsTemplate: opt.Template,
+ ObjectFormatName: opt.ObjectFormatName,
+ SizeLimit: opt.SizeLimit,
})
if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) {
@@ -637,7 +640,7 @@ func Edit(ctx *context.APIContext) {
}
}
- if opts.MirrorInterval != nil {
+ if opts.MirrorInterval != nil || opts.EnablePrune != nil {
if err := updateMirror(ctx, opts); err != nil {
return
}
@@ -718,7 +721,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
if ctx.Repo.GitRepo == nil && !repo.IsEmpty {
var err error
- ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath())
+ ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err)
return err
@@ -887,6 +890,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
AllowRebase: true,
AllowRebaseMerge: true,
AllowSquash: true,
+ AllowFastForwardOnly: true,
AllowManualMerge: true,
AutodetectManualMerge: false,
AllowRebaseUpdate: true,
@@ -913,6 +917,9 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
if opts.AllowSquash != nil {
config.AllowSquash = *opts.AllowSquash
}
+ if opts.AllowFastForwardOnly != nil {
+ config.AllowFastForwardOnly = *opts.AllowFastForwardOnly
+ }
if opts.AllowManualMerge != nil {
config.AllowManualMerge = *opts.AllowManualMerge
}
@@ -987,7 +994,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
}
if len(units)+len(deleteUnitTypes) > 0 {
- if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
+ if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err
}
@@ -1164,12 +1171,11 @@ func GetIssueTemplates(ctx *context.APIContext) {
// "$ref": "#/responses/IssueTemplates"
// "404":
// "$ref": "#/responses/notFound"
- ret, err := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetTemplatesFromDefaultBranch", err)
- return
+ ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
+ if cnt := len(ret.TemplateErrors); cnt != 0 {
+ ctx.Resp.Header().Add("X-Gitea-Warning", "error occurs when parsing issue template: count="+strconv.Itoa(cnt))
}
- ctx.JSON(http.StatusOK, ret)
+ ctx.JSON(http.StatusOK, ret.IssueTemplates)
}
// GetIssueConfig returns the issue config for a repo
diff --git a/routers/api/v1/repo/repo_test.go b/routers/api/v1/repo/repo_test.go
index 29e2d1f21d9..8d6ca9e3b54 100644
--- a/routers/api/v1/repo/repo_test.go
+++ b/routers/api/v1/repo/repo_test.go
@@ -9,9 +9,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
@@ -35,6 +35,7 @@ func TestRepoEdit(t *testing.T) {
allowRebase := false
allowRebaseMerge := false
allowSquashMerge := false
+ allowFastForwardOnlyMerge := false
archived := true
opts := api.EditRepoOption{
Name: &ctx.Repo.Repository.Name,
@@ -50,6 +51,7 @@ func TestRepoEdit(t *testing.T) {
AllowRebase: &allowRebase,
AllowRebaseMerge: &allowRebaseMerge,
AllowSquash: &allowSquashMerge,
+ AllowFastForwardOnly: &allowFastForwardOnlyMerge,
Archived: &archived,
}
diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go
new file mode 100644
index 00000000000..fe133b311d5
--- /dev/null
+++ b/routers/api/v1/repo/runners.go
@@ -0,0 +1,34 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
+)
+
+// GetRegistrationToken returns the token to register repo runners
+func GetRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
+ // ---
+ // summary: Get a repository's actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
+}
diff --git a/routers/api/v1/repo/star.go b/routers/api/v1/repo/star.go
index 05227e33a0c..99676de119c 100644
--- a/routers/api/v1/repo/star.go
+++ b/routers/api/v1/repo/star.go
@@ -7,9 +7,9 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go
index 926d91ca814..53711bedebf 100644
--- a/routers/api/v1/repo/status.go
+++ b/routers/api/v1/repo/status.go
@@ -7,11 +7,12 @@ import (
"fmt"
"net/http"
+ "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
files_service "code.gitea.io/gitea/services/repository/files"
)
@@ -194,8 +195,10 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
listOptions := utils.GetListOptions(ctx)
- statuses, maxResults, err := git_model.GetCommitStatuses(ctx, repo, sha, &git_model.CommitStatusOptions{
+ statuses, maxResults, err := db.FindAndCount[git_model.CommitStatus](ctx, &git_model.CommitStatusOptions{
ListOptions: listOptions,
+ RepoID: repo.ID,
+ SHA: sha,
SortType: ctx.FormTrim("sort"),
State: ctx.FormTrim("state"),
})
diff --git a/routers/api/v1/repo/subscriber.go b/routers/api/v1/repo/subscriber.go
index 05509fc4435..85841828578 100644
--- a/routers/api/v1/repo/subscriber.go
+++ b/routers/api/v1/repo/subscriber.go
@@ -7,9 +7,9 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go
index 2f19f95e66d..a6908f36157 100644
--- a/routers/api/v1/repo/tag.go
+++ b/routers/api/v1/repo/tag.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
releaseservice "code.gitea.io/gitea/services/release"
)
diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go
index 1bacc712118..0ecf3a39d8f 100644
--- a/routers/api/v1/repo/teams.go
+++ b/routers/api/v1/repo/teams.go
@@ -8,7 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/models/organization"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go
index d662b9b5832..1d8e675bde0 100644
--- a/routers/api/v1/repo/topic.go
+++ b/routers/api/v1/repo/topic.go
@@ -8,11 +8,11 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index b3120f4be08..4f05c0df518 100644
--- a/routers/api/v1/repo/transfer.go
+++ b/routers/api/v1/repo/transfer.go
@@ -13,10 +13,10 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
)
@@ -230,5 +230,5 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
}
- return models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
+ return repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
}
diff --git a/routers/api/v1/repo/tree.go b/routers/api/v1/repo/tree.go
index f63100b6ea2..353a996d5b0 100644
--- a/routers/api/v1/repo/tree.go
+++ b/routers/api/v1/repo/tree.go
@@ -6,7 +6,7 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
files_service "code.gitea.io/gitea/services/repository/files"
)
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index 8e5ecce310e..f18ea087c4d 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -10,12 +10,13 @@ import (
"net/url"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
notify_service "code.gitea.io/gitea/services/notify"
wiki_service "code.gitea.io/gitea/services/wiki"
@@ -202,7 +203,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
}
return &api.WikiPage{
- WikiPageMetaData: convert.ToWikiPageMetaData(wikiName, lastCommit, ctx.Repo.Repository),
+ WikiPageMetaData: wiki_service.ToWikiPageMetaData(wikiName, lastCommit, ctx.Repo.Repository),
ContentBase64: content,
CommitCount: commitsCount,
Sidebar: sidebarContent,
@@ -332,7 +333,7 @@ func ListWikiPages(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
return
}
- pages = append(pages, convert.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
+ pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
}
ctx.SetTotalCountHeader(int64(len(entries)))
@@ -475,7 +476,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
// findWikiRepoCommit opens the wiki repo and returns the latest commit, writing to context on error.
// The caller is responsible for closing the returned repo again
func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
- wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
+ wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
if err != nil {
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go
index 02bda1309d7..0ee81b96d5b 100644
--- a/routers/api/v1/settings/settings.go
+++ b/routers/api/v1/settings/settings.go
@@ -6,9 +6,9 @@ package settings
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
// GetGeneralUISettings returns instance's global settings for ui
diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go
new file mode 100644
index 00000000000..c850ad7866a
--- /dev/null
+++ b/routers/api/v1/shared/runners.go
@@ -0,0 +1,32 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package shared
+
+import (
+ "errors"
+ "net/http"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+)
+
+// RegistrationToken is response related to registeration token
+// swagger:response RegistrationToken
+type RegistrationToken struct {
+ Token string `json:"token"`
+}
+
+func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
+ token, err := actions_model.GetLatestRunnerToken(ctx, ownerID, repoID)
+ if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) {
+ token, err = actions_model.NewRunnerToken(ctx, ownerID, repoID)
+ }
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
+}
diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go
index cbe332a7798..babb8c0cf7a 100644
--- a/routers/api/v1/user/action.go
+++ b/routers/api/v1/user/action.go
@@ -7,10 +7,10 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
secret_service "code.gitea.io/gitea/services/secrets"
)
diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go
index f045fb4d5d8..88e314ed31b 100644
--- a/routers/api/v1/user/app.go
+++ b/routers/api/v1/user/app.go
@@ -13,10 +13,10 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/avatar.go b/routers/api/v1/user/avatar.go
index 1c1bb6181aa..f912296228e 100644
--- a/routers/api/v1/user/avatar.go
+++ b/routers/api/v1/user/avatar.go
@@ -7,9 +7,9 @@ import (
"encoding/base64"
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)
diff --git a/routers/api/v1/user/email.go b/routers/api/v1/user/email.go
index 68f6c974a59..33aa851a807 100644
--- a/routers/api/v1/user/email.go
+++ b/routers/api/v1/user/email.go
@@ -8,11 +8,11 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
+ user_service "code.gitea.io/gitea/services/user"
)
// ListEmails list all of the authenticated user's email addresses
@@ -56,22 +56,14 @@ func AddEmail(ctx *context.APIContext) {
// "$ref": "#/responses/EmailList"
// "422":
// "$ref": "#/responses/validationError"
+
form := web.GetForm(ctx).(*api.CreateEmailOption)
if len(form.Emails) == 0 {
ctx.Error(http.StatusUnprocessableEntity, "", "Email list empty")
return
}
- emails := make([]*user_model.EmailAddress, len(form.Emails))
- for i := range form.Emails {
- emails[i] = &user_model.EmailAddress{
- UID: ctx.Doer.ID,
- Email: form.Emails[i],
- IsActivated: !setting.Service.RegisterEmailConfirm,
- }
- }
-
- if err := user_model.AddEmailAddresses(ctx, emails); err != nil {
+ if err := user_service.AddEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
if user_model.IsErrEmailAlreadyUsed(err) {
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
@@ -91,11 +83,17 @@ func AddEmail(ctx *context.APIContext) {
return
}
- apiEmails := make([]*api.Email, len(emails))
- for i := range emails {
- apiEmails[i] = convert.ToEmail(emails[i])
+ emails, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetEmailAddresses", err)
+ return
}
- ctx.JSON(http.StatusCreated, &apiEmails)
+
+ apiEmails := make([]*api.Email, 0, len(emails))
+ for _, email := range emails {
+ apiEmails = append(apiEmails, convert.ToEmail(email))
+ }
+ ctx.JSON(http.StatusCreated, apiEmails)
}
// DeleteEmail delete email
@@ -115,26 +113,19 @@ func DeleteEmail(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
+
form := web.GetForm(ctx).(*api.DeleteEmailOption)
if len(form.Emails) == 0 {
ctx.Status(http.StatusNoContent)
return
}
- emails := make([]*user_model.EmailAddress, len(form.Emails))
- for i := range form.Emails {
- emails[i] = &user_model.EmailAddress{
- Email: form.Emails[i],
- UID: ctx.Doer.ID,
- }
- }
-
- if err := user_model.DeleteEmailAddresses(ctx, emails); err != nil {
+ if err := user_service.DeleteEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
if user_model.IsErrEmailAddressNotExist(err) {
ctx.Error(http.StatusNotFound, "DeleteEmailAddresses", err)
- return
+ } else {
+ ctx.Error(http.StatusInternalServerError, "DeleteEmailAddresses", err)
}
- ctx.Error(http.StatusInternalServerError, "DeleteEmailAddresses", err)
return
}
ctx.Status(http.StatusNoContent)
diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go
index 5815ed4f0b4..398c6b25673 100644
--- a/routers/api/v1/user/follower.go
+++ b/routers/api/v1/user/follower.go
@@ -8,9 +8,9 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go
index 4f8bcaca3e6..b8438cd2aaf 100644
--- a/routers/api/v1/user/gpg_key.go
+++ b/routers/api/v1/user/gpg_key.go
@@ -10,31 +10,33 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) {
- keys, err := asymkey_model.ListGPGKeys(ctx, uid, listOptions)
+ keys, total, err := db.FindAndCount[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ ListOptions: listOptions,
+ OwnerID: uid,
+ })
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
return
}
+ if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
+ return
+ }
+
apiKeys := make([]*api.GPGKey, len(keys))
for i := range keys {
apiKeys[i] = convert.ToGPGKey(keys[i])
}
- total, err := asymkey_model.CountUserGPGKeys(ctx, uid)
- if err != nil {
- ctx.InternalServerError(err)
- return
- }
-
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &apiKeys)
}
@@ -121,6 +123,10 @@ func GetGPGKey(ctx *context.APIContext) {
}
return
}
+ if err := key.LoadSubKeys(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadSubKeys", err)
+ return
+ }
ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
}
@@ -198,7 +204,10 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err)
}
- key, err := asymkey_model.GetGPGKeysByKeyID(ctx, form.KeyID)
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ KeyID: form.KeyID,
+ IncludeSubKeys: true,
+ })
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
ctx.NotFound()
@@ -207,7 +216,7 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
}
return
}
- ctx.JSON(http.StatusOK, convert.ToGPGKey(key[0]))
+ ctx.JSON(http.StatusOK, convert.ToGPGKey(keys[0]))
}
// swagger:parameters userCurrentPostGPGKey
diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go
index 392b266ebd8..8b5c64e2919 100644
--- a/routers/api/v1/user/helper.go
+++ b/routers/api/v1/user/helper.go
@@ -7,7 +7,7 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// GetUserByParamsName get user by name
diff --git a/routers/api/v1/user/hook.go b/routers/api/v1/user/hook.go
index e87385e4a26..9d9ca5bf010 100644
--- a/routers/api/v1/user/hook.go
+++ b/routers/api/v1/user/hook.go
@@ -6,10 +6,10 @@ package user
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go
index dd185aa7d62..ada6759f8e6 100644
--- a/routers/api/v1/user/key.go
+++ b/routers/api/v1/user/key.go
@@ -11,13 +11,13 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go
index b8b2d265bf7..81f8e0f3fe9 100644
--- a/routers/api/v1/user/repo.go
+++ b/routers/api/v1/user/repo.go
@@ -11,9 +11,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/runners.go b/routers/api/v1/user/runners.go
new file mode 100644
index 00000000000..899218473ee
--- /dev/null
+++ b/routers/api/v1/user/runners.go
@@ -0,0 +1,26 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package user
+
+import (
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
+)
+
+// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
+
+// GetRegistrationToken returns the token to register user runners
+func GetRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation GET /user/actions/runners/registration-token user userGetRunnerRegistrationToken
+ // ---
+ // summary: Get an user's actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
+}
diff --git a/routers/api/v1/user/settings.go b/routers/api/v1/user/settings.go
index 53794c82f83..d0a8daaa85b 100644
--- a/routers/api/v1/user/settings.go
+++ b/routers/api/v1/user/settings.go
@@ -6,11 +6,12 @@ package user
import (
"net/http"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/optional"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
+ user_service "code.gitea.io/gitea/services/user"
)
// GetUserSettings returns user settings
@@ -44,36 +45,18 @@ func UpdateUserSettings(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.UserSettingsOptions)
- if form.FullName != nil {
- ctx.Doer.FullName = *form.FullName
+ opts := &user_service.UpdateOptions{
+ FullName: optional.FromPtr(form.FullName),
+ Description: optional.FromPtr(form.Description),
+ Website: optional.FromPtr(form.Website),
+ Location: optional.FromPtr(form.Location),
+ Language: optional.FromPtr(form.Language),
+ Theme: optional.FromPtr(form.Theme),
+ DiffViewStyle: optional.FromPtr(form.DiffViewStyle),
+ KeepEmailPrivate: optional.FromPtr(form.HideEmail),
+ KeepActivityPrivate: optional.FromPtr(form.HideActivity),
}
- if form.Description != nil {
- ctx.Doer.Description = *form.Description
- }
- if form.Website != nil {
- ctx.Doer.Website = *form.Website
- }
- if form.Location != nil {
- ctx.Doer.Location = *form.Location
- }
- if form.Language != nil {
- ctx.Doer.Language = *form.Language
- }
- if form.Theme != nil {
- ctx.Doer.Theme = *form.Theme
- }
- if form.DiffViewStyle != nil {
- ctx.Doer.DiffViewStyle = *form.DiffViewStyle
- }
-
- if form.HideEmail != nil {
- ctx.Doer.KeepEmailPrivate = *form.HideEmail
- }
- if form.HideActivity != nil {
- ctx.Doer.KeepActivityPrivate = *form.HideActivity
- }
-
- if err := user_model.UpdateUser(ctx, ctx.Doer, false); err != nil {
+ if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
ctx.InternalServerError(err)
return
}
diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go
index 2659789ddd9..e624884db36 100644
--- a/routers/api/v1/user/star.go
+++ b/routers/api/v1/user/star.go
@@ -12,9 +12,9 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go
index fb8f67d0722..09147cd2ae0 100644
--- a/routers/api/v1/user/user.go
+++ b/routers/api/v1/user/user.go
@@ -9,8 +9,8 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go
index 7f531eafaa6..706f4cc66bc 100644
--- a/routers/api/v1/user/watch.go
+++ b/routers/api/v1/user/watch.go
@@ -11,9 +11,9 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go
index 32f5c85319d..4e25137817e 100644
--- a/routers/api/v1/utils/git.go
+++ b/routers/api/v1/utils/git.go
@@ -8,9 +8,10 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
// ResolveRefOrSha resolve ref to sha if exist
@@ -69,27 +70,28 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str
return "", "", nil
}
-// ConvertToSHA1 returns a full-length SHA1 from a potential ID string
-func ConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) (git.SHA1, error) {
- if len(commitID) == git.SHAFullLength && git.IsValidSHAPattern(commitID) {
- sha1, err := git.NewIDFromString(commitID)
+// ConvertToObjectID returns a full-length SHA1 from a potential ID string
+func ConvertToObjectID(ctx gocontext.Context, repo *context.Repository, commitID string) (git.ObjectID, error) {
+ objectFormat := repo.GetObjectFormat()
+ if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
+ sha, err := git.NewIDFromString(commitID)
if err == nil {
- return sha1, nil
+ return sha, nil
}
}
- gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.Repository.RepoPath())
+ gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo.Repository)
if err != nil {
- return git.SHA1{}, fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
+ return objectFormat.EmptyObjectID(), fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
}
defer closer.Close()
- return gitRepo.ConvertToSHA1(commitID)
+ return gitRepo.ConvertToGitID(commitID)
}
// MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1
func MustConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) string {
- sha, err := ConvertToSHA1(ctx, repo, commitID)
+ sha, err := ConvertToObjectID(ctx, repo, commitID)
if err != nil {
return commitID
}
diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go
index 1207be25acc..f1abd49a7d6 100644
--- a/routers/api/v1/utils/hook.go
+++ b/routers/api/v1/utils/hook.go
@@ -6,17 +6,18 @@ package utils
import (
"fmt"
"net/http"
+ "strconv"
"strings"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
+ "code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
)
@@ -157,6 +158,7 @@ func pullHook(events []string, event string) bool {
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
// an error, write to `ctx` accordingly. Return (webhook, ok)
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
+ var isSystemWebhook bool
if !checkCreateHookOption(ctx, form) {
return nil, false
}
@@ -164,13 +166,22 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
if len(form.Events) == 0 {
form.Events = []string{"push"}
}
+ if form.Config["is_system_webhook"] != "" {
+ sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
+ if err != nil {
+ ctx.Error(http.StatusUnprocessableEntity, "", "Invalid is_system_webhook value")
+ return nil, false
+ }
+ isSystemWebhook = sw
+ }
w := &webhook.Webhook{
- OwnerID: ownerID,
- RepoID: repoID,
- URL: form.Config["url"],
- ContentType: webhook.ToHookContentType(form.Config["content_type"]),
- Secret: form.Config["secret"],
- HTTPMethod: "POST",
+ OwnerID: ownerID,
+ RepoID: repoID,
+ URL: form.Config["url"],
+ ContentType: webhook.ToHookContentType(form.Config["content_type"]),
+ Secret: form.Config["secret"],
+ HTTPMethod: "POST",
+ IsSystemWebhook: isSystemWebhook,
HookEvent: &webhook_module.HookEvent{
ChooseEvents: true,
HookEvents: webhook_module.HookEvents{
diff --git a/routers/api/v1/utils/page.go b/routers/api/v1/utils/page.go
index 6910b829319..024ba7b8d92 100644
--- a/routers/api/v1/utils/page.go
+++ b/routers/api/v1/utils/page.go
@@ -5,7 +5,7 @@ package utils
import (
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/common/auth.go b/routers/common/auth.go
index 8904785d51f..115d65ed104 100644
--- a/routers/common/auth.go
+++ b/routers/common/auth.go
@@ -5,9 +5,9 @@ package common
import (
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
auth_service "code.gitea.io/gitea/services/auth"
+ "code.gitea.io/gitea/services/context"
)
type AuthResult struct {
diff --git a/routers/common/db.go b/routers/common/db.go
index 547f727ce24..a67c9582fa3 100644
--- a/routers/common/db.go
+++ b/routers/common/db.go
@@ -37,7 +37,6 @@ func InitDBEngine(ctx context.Context) (err error) {
log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second))
time.Sleep(setting.Database.DBConnectBackoff)
}
- db.HasEngine = true
config.SetDynGetter(system_model.NewDatabaseDynKeyGetter())
return nil
}
diff --git a/routers/common/errpage.go b/routers/common/errpage.go
index 923421a29c8..402ca44c12d 100644
--- a/routers/common/errpage.go
+++ b/routers/common/errpage.go
@@ -9,13 +9,13 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/modules/web/routing"
+ "code.gitea.io/gitea/services/context"
)
const tplStatus500 base.TplName = "status/500"
diff --git a/routers/common/markup.go b/routers/common/markup.go
index aaedc13de9b..7819ee72276 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -9,11 +9,11 @@ import (
"net/http"
"strings"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"mvdan.cc/xurls/v2"
)
@@ -32,8 +32,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
case "markdown":
// Raw markdown
if err := markdown.RenderRaw(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: urlPrefix,
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: urlPrefix,
+ },
}, strings.NewReader(text), ctx.Resp); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
}
@@ -75,8 +77,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
}
if err := markup.Render(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: urlPrefix,
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: urlPrefix,
+ },
Metas: meta,
IsWiki: wiki,
Type: markupType,
diff --git a/routers/common/middleware.go b/routers/common/middleware.go
index 8a39dda1798..1ee4c629adb 100644
--- a/routers/common/middleware.go
+++ b/routers/common/middleware.go
@@ -9,11 +9,11 @@ import (
"strings"
"code.gitea.io/gitea/modules/cache"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/modules/web/routing"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/session"
"github.com/chi-middleware/proxy"
diff --git a/routers/common/serve.go b/routers/common/serve.go
index 8a7f8b33321..446908db75d 100644
--- a/routers/common/serve.go
+++ b/routers/common/serve.go
@@ -7,11 +7,11 @@ import (
"io"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
// ServeBlob download a git.Blob
diff --git a/routers/init.go b/routers/init.go
index c1cfe26bc4c..e0a7150ba31 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -45,6 +45,7 @@ import (
repo_migrations "code.gitea.io/gitea/services/migrations"
mirror_service "code.gitea.io/gitea/services/mirror"
pull_service "code.gitea.io/gitea/services/pull"
+ release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/services/repository/archiver"
"code.gitea.io/gitea/services/task"
@@ -118,7 +119,7 @@ func InitWebInstalled(ctx context.Context) {
mustInit(storage.Init)
mailer.NewContext(ctx)
- mustInit(cache.NewContext)
+ mustInit(cache.Init)
mustInit(feed_service.Init)
mustInit(uinotification.Init)
mustInitCtx(ctx, archiver.Init)
@@ -138,6 +139,8 @@ func InitWebInstalled(ctx context.Context) {
mustInit(system.Init)
mustInitCtx(ctx, oauth2.Init)
+ mustInit(release_service.Init)
+
mustInitCtx(ctx, models.Init)
mustInitCtx(ctx, authmodel.Init)
mustInitCtx(ctx, repo_service.Init)
diff --git a/routers/install/install.go b/routers/install/install.go
index 5c0290d2ccc..9c6a8849b63 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -7,6 +7,7 @@ package install
import (
"fmt"
"net/http"
+ "net/mail"
"os"
"os/exec"
"path/filepath"
@@ -21,20 +22,20 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/user"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/common"
auth_service "code.gitea.io/gitea/services/auth"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"gitea.com/go-chi/session"
@@ -409,7 +410,7 @@ func SubmitInstall(ctx *context.Context) {
cfg.Section("server").Key("LFS_START_SERVER").SetValue("true")
cfg.Section("lfs").Key("PATH").SetValue(form.LFSRootPath)
var lfsJwtSecret string
- if _, lfsJwtSecret, err = generate.NewJwtSecretBase64(); err != nil {
+ if _, lfsJwtSecret, err = generate.NewJwtSecretWithBase64(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.lfs_jwt_secret_failed", err), tplInstall, &form)
return
}
@@ -419,6 +420,11 @@ func SubmitInstall(ctx *context.Context) {
}
if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
+ if _, err := mail.ParseAddress(form.SMTPFrom); err != nil {
+ ctx.RenderWithErr(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form)
+ return
+ }
+
cfg.Section("mailer").Key("ENABLED").SetValue("true")
cfg.Section("mailer").Key("SMTP_ADDR").SetValue(form.SMTPAddr)
cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
@@ -533,8 +539,8 @@ func SubmitInstall(ctx *context.Context) {
IsAdmin: true,
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsRestricted: util.OptionalBoolFalse,
- IsActive: util.OptionalBoolTrue,
+ IsRestricted: optional.Some(false),
+ IsActive: optional.Some(true),
}
if err = user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
diff --git a/routers/private/actions.go b/routers/private/actions.go
index 886f23b1c24..53c2412308f 100644
--- a/routers/private/actions.go
+++ b/routers/private/actions.go
@@ -12,11 +12,11 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
// GenerateActionsRunnerToken generates a new runner token for a given scope
diff --git a/routers/private/default_branch.go b/routers/private/default_branch.go
index a23e101e9d1..2e323129ef0 100644
--- a/routers/private/default_branch.go
+++ b/routers/private/default_branch.go
@@ -8,9 +8,9 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/private"
+ gitea_context "code.gitea.io/gitea/services/context"
)
// SetDefaultBranch updates the default branch
diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go
index d5a46e4e7fe..4eafe3923df 100644
--- a/routers/private/hook_post_receive.go
+++ b/routers/private/hook_post_receive.go
@@ -10,7 +10,6 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
@@ -18,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ gitea_context "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
@@ -124,7 +124,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
// post update for agit pull request
// FIXME: use pr.Flow to test whether it's an Agit PR or a GH PR
- if git.SupportProcReceive && refFullName.IsPull() {
+ if git.DefaultFeatures.SupportProcReceive && refFullName.IsPull() {
if repo == nil {
repo = loadRepository(ctx, ownerName, repoName)
if ctx.Written() {
@@ -159,8 +159,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
}
// If we've pushed a branch (and not deleted it)
- if newCommitID != git.EmptySHA && refFullName.IsBranch() {
-
+ if !git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() {
// First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo
if repo == nil {
repo = loadRepository(ctx, ownerName, repoName)
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index 54c618b970d..d6988596bfe 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -22,11 +22,11 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/web"
+ gitea_context "code.gitea.io/gitea/services/context"
pull_service "code.gitea.io/gitea/services/pull"
)
@@ -545,7 +545,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName)
case refFullName.IsTag():
preReceiveTag(ourCtx, oldCommitID, newCommitID, refFullName)
- case git.SupportProcReceive && refFullName.IsFor():
+ case git.DefaultFeatures.SupportProcReceive && refFullName.IsFor():
preReceiveFor(ourCtx, oldCommitID, newCommitID, refFullName)
default:
ourCtx.AssertCanWriteCode()
@@ -582,8 +582,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
repo := ctx.Repo.Repository
gitRepo := ctx.Repo.GitRepo
+ objectFormat := ctx.Repo.GetObjectFormat()
- if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA {
+ if branchName == repo.DefaultBranch && newCommitID == objectFormat.EmptyObjectID().String() {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName),
@@ -611,7 +612,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
// First of all we need to enforce absolutely:
//
// 1. Detect and prevent deletion of the branch
- if newCommitID == git.EmptySHA {
+ if newCommitID == objectFormat.EmptyObjectID().String() {
log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from deletion", branchName),
@@ -620,7 +621,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
// 2. Disallow force pushes to protected branches
- if git.EmptySHA != oldCommitID {
+ if oldCommitID != objectFormat.EmptyObjectID().String() {
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env})
if err != nil {
log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
diff --git a/routers/private/hook_proc_receive.go b/routers/private/hook_proc_receive.go
index 55771207706..cee3bbdd121 100644
--- a/routers/private/hook_proc_receive.go
+++ b/routers/private/hook_proc_receive.go
@@ -7,18 +7,18 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/agit"
+ gitea_context "code.gitea.io/gitea/services/context"
)
// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present
func HookProcReceive(ctx *gitea_context.PrivateContext) {
opts := web.GetForm(ctx).(*private.HookOptions)
- if !git.SupportProcReceive {
+ if !git.DefaultFeatures.SupportProcReceive {
ctx.Status(http.StatusNotFound)
return
}
diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go
index 8604789529a..42b8e5abed8 100644
--- a/routers/private/hook_verification.go
+++ b/routers/private/hook_verification.go
@@ -29,7 +29,8 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []
}()
var command *git.Command
- if oldCommitID == git.EmptySHA {
+ objectFormat, _ := repo.GetObjectFormat()
+ if oldCommitID == objectFormat.EmptyObjectID().String() {
// When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all":
// List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits
// So, it only lists the new commits received, doesn't list the commits already present in the receiving repository
@@ -82,7 +83,8 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
- hash := git.MustIDFromString(sha)
+
+ commitID := git.MustIDFromString(sha)
return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha).
Run(&git.RunOpts{
@@ -91,7 +93,7 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
- commit, err := git.CommitFromReader(repo, hash, stdoutReader)
+ commit, err := git.CommitFromReader(repo, commitID, stdoutReader)
if err != nil {
return err
}
diff --git a/routers/private/hook_verification_test.go b/routers/private/hook_verification_test.go
index cd0c05ff50a..04445b8eaf3 100644
--- a/routers/private/hook_verification_test.go
+++ b/routers/private/hook_verification_test.go
@@ -22,14 +22,17 @@ func TestVerifyCommits(t *testing.T) {
defer gitRepo.Close()
assert.NoError(t, err)
+ objectFormat, err := gitRepo.GetObjectFormat()
+ assert.NoError(t, err)
+
testCases := []struct {
base, head string
verified bool
}{
{"72920278f2f999e3005801e5d5b8ab8139d3641c", "d766f2917716d45be24bfa968b8409544941be32", true},
- {git.EmptySHA, "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit
+ {objectFormat.EmptyObjectID().String(), "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit
{"9779d17a04f1e2640583d35703c62460b2d86e0a", "72920278f2f999e3005801e5d5b8ab8139d3641c", false},
- {git.EmptySHA, "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit
+ {objectFormat.EmptyObjectID().String(), "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit
}
for _, tc := range testCases {
diff --git a/routers/private/internal.go b/routers/private/internal.go
index 407edebeede..ede310113ca 100644
--- a/routers/private/internal.go
+++ b/routers/private/internal.go
@@ -8,11 +8,11 @@ import (
"net/http"
"strings"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/binding"
chi_middleware "github.com/go-chi/chi/v5/middleware"
diff --git a/routers/private/internal_repo.go b/routers/private/internal_repo.go
index 5e7e82b03c9..e8ee8ba8ac3 100644
--- a/routers/private/internal_repo.go
+++ b/routers/private/internal_repo.go
@@ -9,10 +9,10 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
- gitea_context "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
+ gitea_context "code.gitea.io/gitea/services/context"
)
// This file contains common functions relating to setting the Repository for the internal routes
@@ -28,7 +28,7 @@ func RepoAssignment(ctx *gitea_context.PrivateContext) context.CancelFunc {
return nil
}
- gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
diff --git a/routers/private/key.go b/routers/private/key.go
index 0096480d6a6..5b8f238a83e 100644
--- a/routers/private/key.go
+++ b/routers/private/key.go
@@ -7,9 +7,9 @@ import (
"net/http"
asymkey_model "code.gitea.io/gitea/models/asymkey"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/services/context"
)
// UpdatePublicKeyInRepo update public key and deploy key updates
diff --git a/routers/private/mail.go b/routers/private/mail.go
index e5e162c8809..c19ee67896a 100644
--- a/routers/private/mail.go
+++ b/routers/private/mail.go
@@ -11,11 +11,11 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer"
)
diff --git a/routers/private/manager.go b/routers/private/manager.go
index 397e6fac7bf..a6aa03e4ec9 100644
--- a/routers/private/manager.go
+++ b/routers/private/manager.go
@@ -8,7 +8,6 @@ import (
"net/http"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/graceful/releasereopen"
"code.gitea.io/gitea/modules/log"
@@ -17,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
)
// ReloadTemplates reloads all the templates
diff --git a/routers/private/manager_process.go b/routers/private/manager_process.go
index 68e4a21805b..9a0298a37c6 100644
--- a/routers/private/manager_process.go
+++ b/routers/private/manager_process.go
@@ -11,10 +11,10 @@ import (
"runtime"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
process_module "code.gitea.io/gitea/modules/process"
+ "code.gitea.io/gitea/services/context"
)
// Processes prints out the processes
diff --git a/routers/private/manager_unix.go b/routers/private/manager_unix.go
index 09ced33b8d7..0c63ebc918f 100644
--- a/routers/private/manager_unix.go
+++ b/routers/private/manager_unix.go
@@ -8,8 +8,8 @@ package private
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/services/context"
)
// Restart causes the server to perform a graceful restart
diff --git a/routers/private/manager_windows.go b/routers/private/manager_windows.go
index bd3c3c30d06..f1b9365f528 100644
--- a/routers/private/manager_windows.go
+++ b/routers/private/manager_windows.go
@@ -8,9 +8,9 @@ package private
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/private"
+ "code.gitea.io/gitea/services/context"
)
// Restart is not implemented for Windows based servers as they can't fork
diff --git a/routers/private/restore_repo.go b/routers/private/restore_repo.go
index 7efc22a3d9b..4e95d3071db 100644
--- a/routers/private/restore_repo.go
+++ b/routers/private/restore_repo.go
@@ -7,9 +7,9 @@ import (
"io"
"net/http"
- myCtx "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/private"
+ myCtx "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/migrations"
)
diff --git a/routers/private/serv.go b/routers/private/serv.go
index 00731947a55..85368a0aed4 100644
--- a/routers/private/serv.go
+++ b/routers/private/serv.go
@@ -14,11 +14,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki"
)
@@ -297,7 +297,7 @@ func ServCommand(ctx *context.PrivateContext) {
}
} else {
// Because of the special ref "refs/for" we will need to delay write permission check
- if git.SupportProcReceive && unitType == unit.TypeCode {
+ if git.DefaultFeatures.SupportProcReceive && unitType == unit.TypeCode {
mode = perm.AccessModeRead
}
diff --git a/routers/private/ssh_log.go b/routers/private/ssh_log.go
index eacfa18f058..5bec632ead9 100644
--- a/routers/private/ssh_log.go
+++ b/routers/private/ssh_log.go
@@ -6,11 +6,11 @@ package private
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
)
// SSHLog hook to response ssh log
diff --git a/routers/utils/utils.go b/routers/utils/utils.go
index d6856fceacd..1f4d11fd3cc 100644
--- a/routers/utils/utils.go
+++ b/routers/utils/utils.go
@@ -11,14 +11,6 @@ import (
"code.gitea.io/gitea/modules/setting"
)
-// RemoveUsernameParameterSuffix returns the username parameter without the (fullname) suffix - leaving just the username
-func RemoveUsernameParameterSuffix(name string) string {
- if index := strings.Index(name, " ("); index >= 0 {
- name = name[:index]
- }
- return name
-}
-
// SanitizeFlashErrorString will sanitize a flash error string
func SanitizeFlashErrorString(x string) string {
return strings.ReplaceAll(html.EscapeString(x), "\n", " ")
diff --git a/routers/utils/utils_test.go b/routers/utils/utils_test.go
index 6d19214c88e..440aad87c6b 100644
--- a/routers/utils/utils_test.go
+++ b/routers/utils/utils_test.go
@@ -11,12 +11,6 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestRemoveUsernameParameterSuffix(t *testing.T) {
- assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar (Foo Bar)"))
- assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar"))
- assert.Equal(t, "", RemoveUsernameParameterSuffix(""))
-}
-
func TestIsExternalURL(t *testing.T) {
setting.AppURL = "https://try.gitea.io/"
type test struct {
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 1838fe190ca..f3f10fd1b82 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -12,26 +12,30 @@ import (
"time"
activities_model "code.gitea.io/gitea/models/activities"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/cron"
"code.gitea.io/gitea/services/forms"
+ release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
)
const (
- tplDashboard base.TplName = "admin/dashboard"
- tplCron base.TplName = "admin/cron"
- tplQueue base.TplName = "admin/queue"
- tplStacktrace base.TplName = "admin/stacktrace"
- tplQueueManage base.TplName = "admin/queue_manage"
- tplStats base.TplName = "admin/stats"
+ tplDashboard base.TplName = "admin/dashboard"
+ tplSystemStatus base.TplName = "admin/system_status"
+ tplSelfCheck base.TplName = "admin/self_check"
+ tplCron base.TplName = "admin/cron"
+ tplQueue base.TplName = "admin/queue"
+ tplStacktrace base.TplName = "admin/stacktrace"
+ tplQueueManage base.TplName = "admin/queue_manage"
+ tplStats base.TplName = "admin/stats"
)
var sysStatus struct {
@@ -69,7 +73,7 @@ var sysStatus struct {
// Garbage collector statistics.
NextGC string // next run in HeapAlloc time (bytes)
- LastGC string // last run in absolute time (ns)
+ LastGCTime string // last run time
PauseTotalNs string
PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
NumGC uint32
@@ -107,7 +111,7 @@ func updateSystemStatus() {
sysStatus.OtherSys = base.FileSize(int64(m.OtherSys))
sysStatus.NextGC = base.FileSize(int64(m.NextGC))
- sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000)
+ sysStatus.LastGCTime = time.Unix(0, int64(m.LastGC)).Format(time.RFC3339)
sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
sysStatus.NumGC = m.NumGC
@@ -129,7 +133,6 @@ func Dashboard(ctx *context.Context) {
ctx.Data["PageIsAdminDashboard"] = true
ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate(ctx)
ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion(ctx)
- // FIXME: update periodically
updateSystemStatus()
ctx.Data["SysStatus"] = sysStatus
ctx.Data["SSH"] = setting.SSH
@@ -137,6 +140,12 @@ func Dashboard(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplDashboard)
}
+func SystemStatus(ctx *context.Context) {
+ updateSystemStatus()
+ ctx.Data["SysStatus"] = sysStatus
+ ctx.HTML(http.StatusOK, tplSystemStatus)
+}
+
// DashboardPost run an admin operation
func DashboardPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.AdminDashboardForm)
@@ -155,6 +164,13 @@ func DashboardPost(ctx *context.Context) {
}
}()
ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started"))
+ case "sync_repo_tags":
+ go func() {
+ if err := release_service.AddAllRepoTagsToSyncQueue(graceful.GetManager().ShutdownContext()); err != nil {
+ log.Error("AddAllRepoTagsToSyncQueue: %v: %v", ctx.Doer.ID, err)
+ }
+ }()
+ ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_tag.started"))
default:
task := cron.GetTask(form.Op)
if task != nil {
@@ -172,6 +188,33 @@ func DashboardPost(ctx *context.Context) {
}
}
+func SelfCheck(ctx *context.Context) {
+ ctx.Data["PageIsAdminSelfCheck"] = true
+ r, err := db.CheckCollationsDefaultEngine()
+ if err != nil {
+ ctx.Flash.Error(fmt.Sprintf("CheckCollationsDefaultEngine: %v", err), true)
+ }
+
+ if r != nil {
+ ctx.Data["DatabaseType"] = setting.Database.Type
+ ctx.Data["DatabaseCheckResult"] = r
+ hasProblem := false
+ if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) {
+ ctx.Data["DatabaseCheckCollationMismatch"] = true
+ hasProblem = true
+ }
+ if !r.IsCollationCaseSensitive(r.DatabaseCollation) {
+ ctx.Data["DatabaseCheckCollationCaseInsensitive"] = true
+ hasProblem = true
+ }
+ ctx.Data["DatabaseCheckInconsistentCollationColumns"] = r.InconsistentCollationColumns
+ hasProblem = hasProblem || len(r.InconsistentCollationColumns) > 0
+
+ ctx.Data["DatabaseCheckHasProblems"] = hasProblem
+ }
+ ctx.HTML(http.StatusOK, tplSelfCheck)
+}
+
func CronTasks(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor.cron")
ctx.Data["PageIsAdminMonitorCron"] = true
diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go
index b6f7bcd2a5b..85833980746 100644
--- a/routers/web/admin/applications.go
+++ b/routers/web/admin/applications.go
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
)
var (
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 2cf63c646d3..ba487d10457 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -27,6 +26,7 @@ import (
pam_service "code.gitea.io/gitea/services/auth/source/pam"
"code.gitea.io/gitea/services/auth/source/smtp"
"code.gitea.io/gitea/services/auth/source/sspi"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"xorm.io/xorm/convert"
@@ -210,16 +210,16 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) {
if util.IsEmptyString(form.SSPISeparatorReplacement) {
ctx.Data["Err_SSPISeparatorReplacement"] = true
- return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.require_error"))
+ return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.require_error"))
}
if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) {
ctx.Data["Err_SSPISeparatorReplacement"] = true
- return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.alpha_dash_dot_error"))
+ return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.alpha_dash_dot_error"))
}
if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) {
ctx.Data["Err_SSPIDefaultLanguage"] = true
- return nil, errors.New(ctx.Tr("form.lang_select_error"))
+ return nil, errors.New(ctx.Locale.TrString("form.lang_select_error"))
}
return &sspi.Source{
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
index c827f2a4f50..2f5f17e2013 100644
--- a/routers/web/admin/config.go
+++ b/routers/web/admin/config.go
@@ -7,24 +7,27 @@ package admin
import (
"net/http"
"net/url"
+ "strconv"
"strings"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting/config"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer"
"gitea.com/go-chi/session"
)
-const tplConfig base.TplName = "admin/config"
+const (
+ tplConfig base.TplName = "admin/config"
+ tplConfigSettings base.TplName = "admin/config_settings"
+)
// SendTestMail send test mail to confirm mail service is OK
func SendTestMail(ctx *context.Context) {
@@ -98,8 +101,9 @@ func shadowPassword(provider, cfgItem string) string {
// Config show admin config page
func Config(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("admin.config")
+ ctx.Data["Title"] = ctx.Tr("admin.config_summary")
ctx.Data["PageIsAdminConfig"] = true
+ ctx.Data["PageIsAdminConfigSummary"] = true
ctx.Data["CustomConf"] = setting.CustomConf
ctx.Data["AppUrl"] = setting.AppURL
@@ -161,23 +165,70 @@ func Config(ctx *context.Context) {
ctx.Data["Loggers"] = log.GetManager().DumpLoggers()
config.GetDynGetter().InvalidateCache()
- ctx.Data["SystemConfig"] = setting.Config()
prepareDeprecatedWarningsAlert(ctx)
ctx.HTML(http.StatusOK, tplConfig)
}
+func ConfigSettings(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("admin.config_settings")
+ ctx.Data["PageIsAdminConfig"] = true
+ ctx.Data["PageIsAdminConfigSettings"] = true
+ ctx.Data["DefaultOpenWithEditorAppsString"] = setting.DefaultOpenWithEditorApps().ToTextareaString()
+ ctx.HTML(http.StatusOK, tplConfigSettings)
+}
+
func ChangeConfig(ctx *context.Context) {
key := strings.TrimSpace(ctx.FormString("key"))
value := ctx.FormString("value")
cfg := setting.Config()
- allowedKeys := container.SetOf(cfg.Picture.DisableGravatar.DynKey(), cfg.Picture.EnableFederatedAvatar.DynKey())
- if !allowedKeys.Contains(key) {
+
+ marshalBool := func(v string) (string, error) {
+ if b, _ := strconv.ParseBool(v); b {
+ return "true", nil
+ }
+ return "false", nil
+ }
+ marshalOpenWithApps := func(value string) (string, error) {
+ lines := strings.Split(value, "\n")
+ var openWithEditorApps setting.OpenWithEditorAppsType
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ displayName, openURL, ok := strings.Cut(line, "=")
+ displayName, openURL = strings.TrimSpace(displayName), strings.TrimSpace(openURL)
+ if !ok || displayName == "" || openURL == "" {
+ continue
+ }
+ openWithEditorApps = append(openWithEditorApps, setting.OpenWithEditorApp{
+ DisplayName: strings.TrimSpace(displayName),
+ OpenURL: strings.TrimSpace(openURL),
+ })
+ }
+ b, err := json.Marshal(openWithEditorApps)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+ }
+ marshallers := map[string]func(string) (string, error){
+ cfg.Picture.DisableGravatar.DynKey(): marshalBool,
+ cfg.Picture.EnableFederatedAvatar.DynKey(): marshalBool,
+ cfg.Repository.OpenWithEditorApps.DynKey(): marshalOpenWithApps,
+ }
+ marshaller, hasMarshaller := marshallers[key]
+ if !hasMarshaller {
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
return
}
- if err := system_model.SetSettings(ctx, map[string]string{key: value}); err != nil {
- log.Error("set setting failed: %v", err)
+ marshaledValue, err := marshaller(value)
+ if err != nil {
+ ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
+ return
+ }
+ if err = system_model.SetSettings(ctx, map[string]string{key: marshaledValue}); err != nil {
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
return
}
diff --git a/routers/web/admin/diagnosis.go b/routers/web/admin/diagnosis.go
index 5637894e6de..020554a35a4 100644
--- a/routers/web/admin/diagnosis.go
+++ b/routers/web/admin/diagnosis.go
@@ -9,8 +9,8 @@ import (
"runtime/pprof"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httplib"
+ "code.gitea.io/gitea/services/context"
)
func MonitorDiagnosis(ctx *context.Context) {
@@ -58,4 +58,11 @@ func MonitorDiagnosis(ctx *context.Context) {
return
}
_ = pprof.Lookup("goroutine").WriteTo(f, 1)
+
+ f, err = zipWriter.CreateHeader(&zip.FileHeader{Name: "heap.dat", Method: zip.Deflate, Modified: time.Now()})
+ if err != nil {
+ ctx.ServerError("Failed to create zip file", err)
+ return
+ }
+ _ = pprof.Lookup("heap").WriteTo(f, 0)
}
diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go
index 59f80035d89..4296d70aeee 100644
--- a/routers/web/admin/emails.go
+++ b/routers/web/admin/emails.go
@@ -11,10 +11,10 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go
index cd8cc29cdfb..8d4c66fdb2b 100644
--- a/routers/web/admin/hooks.go
+++ b/routers/web/admin/hooks.go
@@ -8,9 +8,9 @@ import (
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go
index 99039a2a9f9..36303cbc06e 100644
--- a/routers/web/admin/notice.go
+++ b/routers/web/admin/notice.go
@@ -8,11 +8,12 @@ import (
"net/http"
"strconv"
+ "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/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -55,7 +56,7 @@ func DeleteNotices(ctx *context.Context) {
}
}
- if err := system_model.DeleteNoticesByIDs(ctx, ids); err != nil {
+ if err := db.DeleteByIDs[system_model.Notice](ctx, ids...); err != nil {
ctx.Flash.Error("DeleteNoticesByIDs: " + err.Error())
ctx.Status(http.StatusInternalServerError)
} else {
diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go
index 00131c9e2f7..c5454db71e3 100644
--- a/routers/web/admin/orgs.go
+++ b/routers/web/admin/orgs.go
@@ -8,10 +8,10 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/web/explore"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index f4e1a93a22f..7c16b69a85c 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
)
@@ -108,6 +108,6 @@ func CleanupExpiredData(ctx *context.Context) {
return
}
- ctx.Flash.Success(ctx.Tr("packages.cleanup.success"))
+ ctx.Flash.Success(ctx.Tr("admin.packages.cleanup.success"))
ctx.Redirect(setting.AppSubURL + "/admin/packages")
}
diff --git a/routers/web/admin/queue.go b/routers/web/admin/queue.go
index 18a8d7d3e68..d8c50730b14 100644
--- a/routers/web/admin/queue.go
+++ b/routers/web/admin/queue.go
@@ -7,9 +7,9 @@ import (
"net/http"
"strconv"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
func Queues(ctx *context.Context) {
diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go
index ee7336c7543..7d99c7a12e8 100644
--- a/routers/web/admin/repos.go
+++ b/routers/web/admin/repos.go
@@ -12,12 +12,12 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/explore"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/admin/runners.go b/routers/web/admin/runners.go
index eaa268b4f14..d73290a8dba 100644
--- a/routers/web/admin/runners.go
+++ b/routers/web/admin/runners.go
@@ -4,8 +4,8 @@
package admin
import (
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
func RedirectToDefaultSetting(ctx *context.Context) {
diff --git a/routers/web/admin/stacktrace.go b/routers/web/admin/stacktrace.go
index b603fb59a26..d6def94bb49 100644
--- a/routers/web/admin/stacktrace.go
+++ b/routers/web/admin/stacktrace.go
@@ -7,9 +7,9 @@ import (
"net/http"
"runtime"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// Stacktrace show admin monitor goroutines page
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index 44c4fa75125..cbca26eba8c 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -5,6 +5,7 @@
package admin
import (
+ "errors"
"net/http"
"net/url"
"strconv"
@@ -18,13 +19,14 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/explore"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
user_service "code.gitea.io/gitea/services/user"
@@ -138,7 +140,7 @@ func NewUserPost(ctx *context.Context) {
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolTrue,
+ IsActive: optional.Some(true),
Visibility: &form.Visibility,
}
@@ -162,11 +164,10 @@ func NewUserPost(ctx *context.Context) {
ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserNew, &form)
return
}
- pwned, err := password.IsPwned(ctx, form.Password)
- if pwned {
+ if err := password.IsPwned(ctx, form.Password); err != nil {
ctx.Data["Err_Password"] = true
errMsg := ctx.Tr("auth.password_pwned")
- if err != nil {
+ if password.IsErrIsPwnedRequest(err) {
log.Error(err.Error())
errMsg = ctx.Tr("auth.password_pwned_err")
}
@@ -184,10 +185,7 @@ func NewUserPost(ctx *context.Context) {
case user_model.IsErrEmailAlreadyUsed(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
- case user_model.IsErrEmailCharIsNotSupported(err):
- ctx.Data["Err_Email"] = true
- ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
- case user_model.IsErrEmailInvalid(err):
+ case user_model.IsErrEmailInvalid(err), user_model.IsErrEmailCharIsNotSupported(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
case db.IsErrNameReserved(err):
@@ -348,68 +346,111 @@ func EditUserPost(ctx *context.Context) {
return
}
+ if form.UserName != "" {
+ if err := user_service.RenameUser(ctx, u, form.UserName); err != nil {
+ switch {
+ case user_model.IsErrUserIsNotLocal(err):
+ ctx.Data["Err_UserName"] = true
+ ctx.RenderWithErr(ctx.Tr("form.username_change_not_local_user"), tplUserEdit, &form)
+ case user_model.IsErrUserAlreadyExist(err):
+ ctx.Data["Err_UserName"] = true
+ ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplUserEdit, &form)
+ case db.IsErrNameReserved(err):
+ ctx.Data["Err_UserName"] = true
+ ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", form.UserName), tplUserEdit, &form)
+ case db.IsErrNamePatternNotAllowed(err):
+ ctx.Data["Err_UserName"] = true
+ ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", form.UserName), tplUserEdit, &form)
+ case db.IsErrNameCharsNotAllowed(err):
+ ctx.Data["Err_UserName"] = true
+ ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", form.UserName), tplUserEdit, &form)
+ default:
+ ctx.ServerError("RenameUser", err)
+ }
+ return
+ }
+ }
+
+ authOpts := &user_service.UpdateAuthOptions{
+ Password: optional.FromNonDefault(form.Password),
+ LoginName: optional.Some(form.LoginName),
+ }
+
+ // skip self Prohibit Login
+ if ctx.Doer.ID == u.ID {
+ authOpts.ProhibitLogin = optional.Some(false)
+ } else {
+ authOpts.ProhibitLogin = optional.Some(form.ProhibitLogin)
+ }
+
fields := strings.Split(form.LoginType, "-")
if len(fields) == 2 {
- loginType, _ := strconv.ParseInt(fields[0], 10, 0)
authSource, _ := strconv.ParseInt(fields[1], 10, 64)
- if u.LoginSource != authSource {
- u.LoginSource = authSource
- u.LoginType = auth.Type(loginType)
- }
+ authOpts.LoginSource = optional.Some(authSource)
}
- if len(form.Password) > 0 && (u.IsLocal() || u.IsOAuth2()) {
- var err error
- if len(form.Password) < setting.MinPasswordLength {
+ if err := user_service.UpdateAuth(ctx, u, authOpts); err != nil {
+ switch {
+ case errors.Is(err, password.ErrMinLength):
ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserEdit, &form)
- return
- }
- if !password.IsComplexEnough(form.Password) {
- ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserEdit, &form)
- return
- }
- pwned, err := password.IsPwned(ctx, form.Password)
- if pwned {
+ case errors.Is(err, password.ErrComplexity):
ctx.Data["Err_Password"] = true
- errMsg := ctx.Tr("auth.password_pwned")
- if err != nil {
- log.Error(err.Error())
- errMsg = ctx.Tr("auth.password_pwned_err")
- }
- ctx.RenderWithErr(errMsg, tplUserEdit, &form)
- return
- }
-
- if err := user_model.ValidateEmail(form.Email); err != nil {
- ctx.Data["Err_Email"] = true
- ctx.RenderWithErr(ctx.Tr("form.email_error"), tplUserEdit, &form)
- return
- }
-
- if u.Salt, err = user_model.GetUserSalt(); err != nil {
+ ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserEdit, &form)
+ case errors.Is(err, password.ErrIsPwned):
+ ctx.Data["Err_Password"] = true
+ ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplUserEdit, &form)
+ case password.IsErrIsPwnedRequest(err):
+ log.Error("%s", err.Error())
+ ctx.Data["Err_Password"] = true
+ ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form)
+ default:
ctx.ServerError("UpdateUser", err)
- return
}
- if err = u.SetPassword(form.Password); err != nil {
- ctx.ServerError("SetPassword", err)
+ return
+ }
+
+ if form.Email != "" {
+ if err := user_service.AddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
+ switch {
+ case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
+ ctx.Data["Err_Email"] = true
+ ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
+ case user_model.IsErrEmailAlreadyUsed(err):
+ ctx.Data["Err_Email"] = true
+ ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
+ default:
+ ctx.ServerError("AddOrSetPrimaryEmailAddress", err)
+ }
return
}
}
- if len(form.UserName) != 0 && u.Name != form.UserName {
- if err := user_setting.HandleUsernameChange(ctx, u, form.UserName); err != nil {
- if ctx.Written() {
- return
- }
- ctx.RenderWithErr(ctx.Flash.ErrorMsg, tplUserEdit, &form)
- return
- }
- u.Name = form.UserName
- u.LowerName = strings.ToLower(form.UserName)
+ opts := &user_service.UpdateOptions{
+ FullName: optional.Some(form.FullName),
+ Website: optional.Some(form.Website),
+ Location: optional.Some(form.Location),
+ IsActive: optional.Some(form.Active),
+ IsAdmin: optional.Some(form.Admin),
+ AllowGitHook: optional.Some(form.AllowGitHook),
+ AllowImportLocal: optional.Some(form.AllowImportLocal),
+ MaxRepoCreation: optional.Some(form.MaxRepoCreation),
+ AllowCreateOrganization: optional.Some(form.AllowCreateOrganization),
+ IsRestricted: optional.Some(form.Restricted),
+ Visibility: optional.Some(form.Visibility),
}
+ if err := user_service.UpdateUser(ctx, u, opts); err != nil {
+ if models.IsErrDeleteLastAdminUser(err) {
+ ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
+ } else {
+ ctx.ServerError("UpdateUser", err)
+ }
+ return
+ }
+ log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name)
+
if form.Reset2FA {
tf, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
@@ -433,47 +474,8 @@ func EditUserPost(ctx *context.Context) {
return
}
}
-
}
- u.LoginName = form.LoginName
- u.FullName = form.FullName
- emailChanged := !strings.EqualFold(u.Email, form.Email)
- u.Email = form.Email
- u.Website = form.Website
- u.Location = form.Location
- u.MaxRepoCreation = form.MaxRepoCreation
- u.IsActive = form.Active
- u.IsAdmin = form.Admin
- u.IsRestricted = form.Restricted
- u.AllowGitHook = form.AllowGitHook
- u.AllowImportLocal = form.AllowImportLocal
- u.AllowCreateOrganization = form.AllowCreateOrganization
-
- u.Visibility = form.Visibility
-
- // skip self Prohibit Login
- if ctx.Doer.ID == u.ID {
- u.ProhibitLogin = false
- } else {
- u.ProhibitLogin = form.ProhibitLogin
- }
-
- if err := user_model.UpdateUser(ctx, u, emailChanged); err != nil {
- if user_model.IsErrEmailAlreadyUsed(err) {
- ctx.Data["Err_Email"] = true
- ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
- } else if user_model.IsErrEmailCharIsNotSupported(err) ||
- user_model.IsErrEmailInvalid(err) {
- ctx.Data["Err_Email"] = true
- ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
- } else {
- ctx.ServerError("UpdateUser", err)
- }
- return
- }
- log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name)
-
ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
}
@@ -503,7 +505,10 @@ func DeleteUser(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages"))
- ctx.Redirect(setting.AppSubURL + "/admin/users/" + ctx.Params(":userid"))
+ ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
+ case models.IsErrDeleteLastAdminUser(err):
+ ctx.Flash.Error(ctx.Tr("auth.last_admin"))
+ ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
default:
ctx.ServerError("DeleteUser", err)
}
diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go
index 560ee70ea04..f6f92378581 100644
--- a/routers/web/admin/users_test.go
+++ b/routers/web/admin/users_test.go
@@ -8,10 +8,10 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert"
diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go
index dc0062ebaa5..f93177bf96b 100644
--- a/routers/web/auth/2fa.go
+++ b/routers/web/auth/2fa.go
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 0ea91fc759a..7704a110a67 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -7,6 +7,7 @@ package auth
import (
"errors"
"fmt"
+ "html/template"
"net/http"
"strings"
@@ -15,9 +16,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -27,28 +28,24 @@ import (
"code.gitea.io/gitea/routers/utils"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
+ user_service "code.gitea.io/gitea/services/user"
"github.com/markbates/goth"
)
const (
- // tplSignIn template for sign in page
- tplSignIn base.TplName = "user/auth/signin"
- // tplSignUp template path for sign up page
- tplSignUp base.TplName = "user/auth/signup"
- // TplActivate template path for activate user
- TplActivate base.TplName = "user/auth/activate"
+ tplSignIn base.TplName = "user/auth/signin" // for sign in page
+ tplSignUp base.TplName = "user/auth/signup" // for sign up page
+ TplActivate base.TplName = "user/auth/activate" // for activate user
+ TplActivatePrompt base.TplName = "user/auth/activate_prompt" // for showing a message for user activation
)
// autoSignIn reads cookie and try to auto-login.
func autoSignIn(ctx *context.Context) (bool, error) {
- if !db.HasEngine {
- return false, nil
- }
-
isSucceed := false
defer func() {
if !isSucceed {
@@ -108,9 +105,11 @@ func autoSignIn(ctx *context.Context) (bool, error) {
func resetLocale(ctx *context.Context, u *user_model.User) error {
// Language setting of the user overwrites the one previously set
// If the user does not have a locale set, we save the current one.
- if len(u.Language) == 0 {
- u.Language = ctx.Locale.Language()
- if err := user_model.UpdateUserCols(ctx, u, "language"); err != nil {
+ if u.Language == "" {
+ opts := &user_service.UpdateOptions{
+ Language: optional.Some(ctx.Locale.Language()),
+ }
+ if err := user_service.UpdateUser(ctx, u, opts); err != nil {
return err
}
}
@@ -145,7 +144,11 @@ func CheckAutoLogin(ctx *context.Context) bool {
if isSucceed {
middleware.DeleteRedirectToCookie(ctx.Resp)
- ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))
+ nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL)
+ if setting.LandingPageURL == setting.LandingPageLogin {
+ nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page
+ }
+ ctx.RedirectToFirst(redirectTo, nextRedirectTo)
return true
}
@@ -330,10 +333,12 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
// Language setting of the user overwrites the one previously set
// If the user does not have a locale set, we save the current one.
- if len(u.Language) == 0 {
- u.Language = ctx.Locale.Language()
- if err := user_model.UpdateUserCols(ctx, u, "language"); err != nil {
- ctx.ServerError("UpdateUserCols Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language))
+ if u.Language == "" {
+ opts := &user_service.UpdateOptions{
+ Language: optional.Some(ctx.Locale.Language()),
+ }
+ if err := user_service.UpdateUser(ctx, u, opts); err != nil {
+ ctx.ServerError("UpdateUser Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, ctx.Locale.Language()))
return setting.AppSubURL + "/"
}
}
@@ -348,9 +353,8 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
ctx.Csrf.DeleteCookie(ctx)
// Register last login
- u.SetLastLogin()
- if err := user_model.UpdateUserCols(ctx, u, "last_login_unix"); err != nil {
- ctx.ServerError("UpdateUserCols", err)
+ if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
+ ctx.ServerError("UpdateUser", err)
return setting.AppSubURL + "/"
}
@@ -368,14 +372,14 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
return setting.AppSubURL + "/"
}
-func getUserName(gothUser *goth.User) string {
+func getUserName(gothUser *goth.User) (string, error) {
switch setting.OAuth2Client.Username {
case setting.OAuth2UsernameEmail:
- return strings.Split(gothUser.Email, "@")[0]
+ return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0])
case setting.OAuth2UsernameNickname:
- return gothUser.NickName
+ return user_model.NormalizeUserName(gothUser.NickName)
default: // OAuth2UsernameUserid
- return gothUser.UserID
+ return gothUser.UserID, nil
}
}
@@ -482,10 +486,9 @@ func SignUpPost(ctx *context.Context) {
ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplSignUp, &form)
return
}
- pwned, err := password.IsPwned(ctx, form.Password)
- if pwned {
+ if err := password.IsPwned(ctx, form.Password); err != nil {
errMsg := ctx.Tr("auth.password_pwned")
- if err != nil {
+ if password.IsErrIsPwnedRequest(err) {
log.Error(err.Error())
errMsg = ctx.Tr("auth.password_pwned_err")
}
@@ -589,10 +592,12 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
// Auto-set admin for the only user.
if user_model.CountUsers(ctx, nil) == 1 {
- u.IsAdmin = true
- u.IsActive = true
- u.SetLastLogin()
- if err := user_model.UpdateUserCols(ctx, u, "is_admin", "is_active", "last_login_unix"); err != nil {
+ opts := &user_service.UpdateOptions{
+ IsActive: optional.Some(true),
+ IsAdmin: optional.Some(true),
+ SetLastLogin: true,
+ }
+ if err := user_service.UpdateUser(ctx, u, opts); err != nil {
ctx.ServerError("UpdateUser", err)
return false
}
@@ -607,76 +612,87 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
}
}
- // Send confirmation email
- if !u.IsActive && u.ID > 1 {
- if setting.Service.RegisterManualConfirm {
- ctx.Data["ManualActivationOnly"] = true
- ctx.HTML(http.StatusOK, TplActivate)
- return false
- }
+ // for active user or the first (admin) user, we don't need to send confirmation email
+ if u.IsActive || u.ID == 1 {
+ return true
+ }
- mailer.SendActivateAccountMail(ctx.Locale, u)
-
- ctx.Data["IsSendRegisterMail"] = true
- ctx.Data["Email"] = u.Email
- ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
- ctx.HTML(http.StatusOK, TplActivate)
-
- if setting.CacheService.Enabled {
- if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
- log.Error("Set cache(MailResendLimit) fail: %v", err)
- }
- }
+ if setting.Service.RegisterManualConfirm {
+ renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.manual_activation_only"))
return false
}
- return true
+ sendActivateEmail(ctx, u)
+ return false
+}
+
+func renderActivationPromptMessage(ctx *context.Context, msg template.HTML) {
+ ctx.Data["ActivationPromptMessage"] = msg
+ ctx.HTML(http.StatusOK, TplActivatePrompt)
+}
+
+func sendActivateEmail(ctx *context.Context, u *user_model.User) {
+ if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
+ renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.resent_limit_prompt"))
+ return
+ }
+
+ if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
+ log.Error("Set cache(MailResendLimit) fail: %v", err)
+ renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.resent_limit_prompt"))
+ return
+ }
+
+ mailer.SendActivateAccountMail(ctx.Locale, u)
+
+ activeCodeLives := timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
+ msgHTML := ctx.Locale.Tr("auth.confirmation_mail_sent_prompt_ex", u.Email, activeCodeLives)
+ renderActivationPromptMessage(ctx, msgHTML)
+}
+
+func renderActivationVerifyPassword(ctx *context.Context, code string) {
+ ctx.Data["ActivationCode"] = code
+ ctx.Data["NeedVerifyLocalPassword"] = true
+ ctx.HTML(http.StatusOK, TplActivate)
+}
+
+func renderActivationChangeEmail(ctx *context.Context) {
+ ctx.HTML(http.StatusOK, TplActivate)
}
// Activate render activate user page
func Activate(ctx *context.Context) {
code := ctx.FormString("code")
- if len(code) == 0 {
- ctx.Data["IsActivatePage"] = true
- if ctx.Doer == nil || ctx.Doer.IsActive {
- ctx.NotFound("invalid user", nil)
+ if code == "" {
+ if ctx.Doer == nil {
+ ctx.Redirect(setting.AppSubURL + "/user/login")
+ return
+ } else if ctx.Doer.IsActive {
+ ctx.Redirect(setting.AppSubURL + "/")
return
}
- // Resend confirmation email.
- if setting.Service.RegisterEmailConfirm {
- if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.Doer.LowerName) {
- ctx.Data["ResendLimited"] = true
- } else {
- ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
- mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer)
- if setting.CacheService.Enabled {
- if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
- log.Error("Set cache(MailResendLimit) fail: %v", err)
- }
- }
- }
- } else {
- ctx.Data["ServiceNotEnabled"] = true
+ if setting.MailService == nil || !setting.Service.RegisterEmailConfirm {
+ renderActivationPromptMessage(ctx, ctx.Tr("auth.disable_register_mail"))
+ return
}
- ctx.HTML(http.StatusOK, TplActivate)
+
+ // Resend confirmation email. FIXME: ideally this should be in a POST request
+ sendActivateEmail(ctx, ctx.Doer)
return
}
+ // TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
user := user_model.VerifyUserActiveCode(ctx, code)
- // if code is wrong
- if user == nil {
- ctx.Data["IsCodeInvalid"] = true
- ctx.HTML(http.StatusOK, TplActivate)
+ if user == nil { // if code is wrong
+ renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code"))
return
}
// if account is local account, verify password
if user.LoginSource == 0 {
- ctx.Data["Code"] = code
- ctx.Data["NeedsPassword"] = true
- ctx.HTML(http.StatusOK, TplActivate)
+ renderActivationVerifyPassword(ctx, code)
return
}
@@ -686,31 +702,49 @@ func Activate(ctx *context.Context) {
// ActivatePost handles account activation with password check
func ActivatePost(ctx *context.Context) {
code := ctx.FormString("code")
- if len(code) == 0 {
+ if ctx.Doer != nil && ctx.Doer.IsActive {
+ ctx.Redirect(setting.AppSubURL + "/user/activate") // it will redirect again to the correct page
+ return
+ }
+
+ if code == "" {
+ newEmail := strings.TrimSpace(ctx.FormString("change_email"))
+ if ctx.Doer != nil && newEmail != "" && !strings.EqualFold(ctx.Doer.Email, newEmail) {
+ if user_model.ValidateEmail(newEmail) != nil {
+ ctx.Flash.Error(ctx.Locale.Tr("form.email_invalid"), true)
+ renderActivationChangeEmail(ctx)
+ return
+ }
+ err := user_model.ChangeInactivePrimaryEmail(ctx, ctx.Doer.ID, ctx.Doer.Email, newEmail)
+ if err != nil {
+ ctx.Flash.Error(ctx.Locale.Tr("admin.emails.not_updated", newEmail), true)
+ renderActivationChangeEmail(ctx)
+ return
+ }
+ ctx.Doer.Email = newEmail
+ }
+ // FIXME: at the moment, GET request handles the "send confirmation email" action. But the old code does this redirect and then send a confirmation email.
ctx.Redirect(setting.AppSubURL + "/user/activate")
return
}
+ // TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
user := user_model.VerifyUserActiveCode(ctx, code)
- // if code is wrong
- if user == nil {
- ctx.Data["IsCodeInvalid"] = true
- ctx.HTML(http.StatusOK, TplActivate)
+ if user == nil { // if code is wrong
+ renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code"))
return
}
// if account is local account, verify password
if user.LoginSource == 0 {
password := ctx.FormString("password")
- if len(password) == 0 {
- ctx.Data["Code"] = code
- ctx.Data["NeedsPassword"] = true
- ctx.HTML(http.StatusOK, TplActivate)
+ if password == "" {
+ renderActivationVerifyPassword(ctx, code)
return
}
if !user.ValidatePassword(password) {
- ctx.Data["IsPasswordInvalid"] = true
- ctx.HTML(http.StatusOK, TplActivate)
+ ctx.Flash.Error(ctx.Locale.Tr("auth.invalid_password"), true)
+ renderActivationVerifyPassword(ctx, code)
return
}
}
@@ -756,10 +790,8 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
return
}
- // Register last login
- user.SetLastLogin()
- if err := user_model.UpdateUserCols(ctx, user, "last_login_unix"); err != nil {
- ctx.ServerError("UpdateUserCols", err)
+ if err := user_service.UpdateUser(ctx, user, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
+ ctx.ServerError("UpdateUser", err)
return
}
@@ -789,7 +821,7 @@ func ActivateEmail(ctx *context.Context) {
if u, err := user_model.GetUserByID(ctx, email.UID); err != nil {
log.Warn("GetUserByID: %d", email.UID)
- } else if setting.CacheService.Enabled {
+ } else {
// Allow user to validate more emails
_ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName)
}
diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go
index f41590dc135..f744a57a43f 100644
--- a/routers/web/auth/linkaccount.go
+++ b/routers/web/auth/linkaccount.go
@@ -12,13 +12,13 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
@@ -55,7 +55,11 @@ func LinkAccount(ctx *context.Context) {
}
gu, _ := gothUser.(goth.User)
- uname := getUserName(&gu)
+ uname, err := getUserName(&gu)
+ if err != nil {
+ ctx.ServerError("UserSignIn", err)
+ return
+ }
email := gu.Email
ctx.Data["user_name"] = uname
ctx.Data["email"] = email
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 21d82cea450..d5ca7397f06 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"html"
+ "html/template"
"io"
"net/http"
"net/url"
@@ -21,9 +22,9 @@ import (
auth_module "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -32,6 +33,7 @@ import (
auth_service "code.gitea.io/gitea/services/auth"
source_service "code.gitea.io/gitea/services/auth/source"
"code.gitea.io/gitea/services/auth/source/oauth2"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
user_service "code.gitea.io/gitea/services/user"
@@ -498,11 +500,11 @@ func AuthorizeOAuth(ctx *context.Context) {
ctx.Data["Scope"] = form.Scope
ctx.Data["Nonce"] = form.Nonce
if user != nil {
- ctx.Data["ApplicationCreatorLinkHTML"] = fmt.Sprintf(`@%s`, html.EscapeString(user.HomeLink()), html.EscapeString(user.Name))
+ ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`@%s`, html.EscapeString(user.HomeLink()), html.EscapeString(user.Name)))
} else {
- ctx.Data["ApplicationCreatorLinkHTML"] = fmt.Sprintf(`%s`, html.EscapeString(setting.AppSubURL+"/"), html.EscapeString(setting.AppName))
+ ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`%s`, html.EscapeString(setting.AppSubURL+"/"), html.EscapeString(setting.AppName)))
}
- ctx.Data["ApplicationRedirectDomainHTML"] = "" + html.EscapeString(form.RedirectURI) + ""
+ ctx.Data["ApplicationRedirectDomainHTML"] = template.HTML("" + html.EscapeString(form.RedirectURI) + "")
// TODO document SESSION <=> FORM
err = ctx.Session.Set("client_id", app.ClientID)
if err != nil {
@@ -578,16 +580,8 @@ func GrantApplicationOAuth(ctx *context.Context) {
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
func OIDCWellKnown(ctx *context.Context) {
- t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown", nil)
- if err != nil {
- ctx.ServerError("unable to find template", err)
- return
- }
- ctx.Resp.Header().Set("Content-Type", "application/json")
ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
- if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
- ctx.ServerError("unable to execute template", err)
- }
+ ctx.JSONTemplate("user/auth/oidc_wellknown")
}
// OIDCKeys generates the JSON Web Key Set
@@ -970,8 +964,13 @@ func SignInOAuthCallback(ctx *context.Context) {
ctx.ServerError("CreateUser", err)
return
}
+ uname, err := getUserName(&gothUser)
+ if err != nil {
+ ctx.ServerError("UserSignIn", err)
+ return
+ }
u = &user_model.User{
- Name: getUserName(&gothUser),
+ Name: uname,
FullName: gothUser.Name,
Email: gothUser.Email,
LoginType: auth.OAuth2,
@@ -980,12 +979,14 @@ func SignInOAuthCallback(ctx *context.Context) {
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
- IsActive: util.OptionalBoolOf(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
+ IsActive: optional.Some(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
}
source := authSource.Cfg.(*oauth2.Source)
- setUserAdminAndRestrictedFromGroupClaims(source, u, &gothUser)
+ isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser)
+ u.IsAdmin = isAdmin.ValueOrDefault(false)
+ u.IsRestricted = isRestricted.ValueOrDefault(false)
if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
// error already handled
@@ -1049,19 +1050,17 @@ func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[
return claimValueToStringSet(groupClaims)
}
-func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_model.User, gothUser *goth.User) bool {
+func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin, isRestricted optional.Option[bool]) {
groups := getClaimedGroups(source, gothUser)
- wasAdmin, wasRestricted := u.IsAdmin, u.IsRestricted
-
if source.AdminGroup != "" {
- u.IsAdmin = groups.Contains(source.AdminGroup)
+ isAdmin = optional.Some(groups.Contains(source.AdminGroup))
}
if source.RestrictedGroup != "" {
- u.IsRestricted = groups.Contains(source.RestrictedGroup)
+ isRestricted = optional.Some(groups.Contains(source.RestrictedGroup))
}
- return wasAdmin != u.IsAdmin || wasRestricted != u.IsRestricted
+ return isAdmin, isRestricted
}
func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
@@ -1128,18 +1127,12 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
// Clear whatever CSRF cookie has right now, force to generate a new one
ctx.Csrf.DeleteCookie(ctx)
- // Register last login
- u.SetLastLogin()
-
- // Update GroupClaims
- changed := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser)
- cols := []string{"last_login_unix"}
- if changed {
- cols = append(cols, "is_admin", "is_restricted")
+ opts := &user_service.UpdateOptions{
+ SetLastLogin: true,
}
-
- if err := user_model.UpdateUserCols(ctx, u, cols...); err != nil {
- ctx.ServerError("UpdateUserCols", err)
+ opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
+ if err := user_service.UpdateUser(ctx, u, opts); err != nil {
+ ctx.ServerError("UpdateUser", err)
return
}
@@ -1172,10 +1165,11 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
return
}
- changed := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser)
- if changed {
- if err := user_model.UpdateUserCols(ctx, u, "is_admin", "is_restricted"); err != nil {
- ctx.ServerError("UpdateUserCols", err)
+ opts := &user_service.UpdateOptions{}
+ opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
+ if opts.IsAdmin.Has() || opts.IsRestricted.Has() {
+ if err := user_service.UpdateUser(ctx, u, opts); err != nil {
+ ctx.ServerError("UpdateUser", err)
return
}
}
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index 29ef772b1c6..2143b8096a0 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -11,12 +11,12 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/auth"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go
index bdfa8c40258..c9e03860412 100644
--- a/routers/web/auth/password.go
+++ b/routers/web/auth/password.go
@@ -12,15 +12,17 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
+ user_service "code.gitea.io/gitea/services/user"
)
var (
@@ -35,7 +37,7 @@ func ForgotPasswd(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
if setting.MailService == nil {
- log.Warn(ctx.Tr("auth.disable_forgot_password_mail_admin"))
+ log.Warn("no mail service configured")
ctx.Data["IsResetDisable"] = true
ctx.HTML(http.StatusOK, tplForgotPassword)
return
@@ -79,7 +81,7 @@ func ForgotPasswdPost(ctx *context.Context) {
return
}
- if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+u.LowerName) {
+ if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
ctx.Data["ResendLimited"] = true
ctx.HTML(http.StatusOK, tplForgotPassword)
return
@@ -87,10 +89,8 @@ func ForgotPasswdPost(ctx *context.Context) {
mailer.SendResetPasswordMail(u)
- if setting.CacheService.Enabled {
- if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
- log.Error("Set cache(MailResendLimit) fail: %v", err)
- }
+ if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
+ log.Error("Set cache(MailResendLimit) fail: %v", err)
}
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
@@ -167,30 +167,6 @@ func ResetPasswdPost(ctx *context.Context) {
return
}
- // Validate password length.
- passwd := ctx.FormString("password")
- if len(passwd) < setting.MinPasswordLength {
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
- return
- } else if !password.IsComplexEnough(passwd) {
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil)
- return
- } else if pwned, err := password.IsPwned(ctx, passwd); pwned || err != nil {
- errMsg := ctx.Tr("auth.password_pwned")
- if err != nil {
- log.Error(err.Error())
- errMsg = ctx.Tr("auth.password_pwned_err")
- }
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(errMsg, tplResetPassword, nil)
- return
- }
-
// Handle two-factor
regenerateScratchToken := false
if twofa != nil {
@@ -223,18 +199,27 @@ func ResetPasswdPost(ctx *context.Context) {
}
}
}
- var err error
- if u.Rands, err = user_model.GetUserSalt(); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
+
+ opts := &user_service.UpdateAuthOptions{
+ Password: optional.Some(ctx.FormString("password")),
+ MustChangePassword: optional.Some(false),
}
- if err = u.SetPassword(passwd); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
- u.MustChangePassword = false
- if err := user_model.UpdateUserCols(ctx, u, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil {
- ctx.ServerError("UpdateUser", err)
+ if err := user_service.UpdateAuth(ctx, u, opts); err != nil {
+ ctx.Data["IsResetForm"] = true
+ ctx.Data["Err_Password"] = true
+ switch {
+ case errors.Is(err, password.ErrMinLength):
+ ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
+ case errors.Is(err, password.ErrComplexity):
+ ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil)
+ case errors.Is(err, password.ErrIsPwned):
+ ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplResetPassword, nil)
+ case password.IsErrIsPwnedRequest(err):
+ log.Error("%s", err.Error())
+ ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil)
+ default:
+ ctx.ServerError("UpdateAuth", err)
+ }
return
}
@@ -244,7 +229,7 @@ func ResetPasswdPost(ctx *context.Context) {
if regenerateScratchToken {
// Invalidate the scratch token.
- _, err = twofa.GenerateScratchToken()
+ _, err := twofa.GenerateScratchToken()
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -284,11 +269,11 @@ func MustChangePasswordPost(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplMustChangePassword)
return
}
- u := ctx.Doer
+
// Make sure only requests for users who are eligible to change their password via
// this method passes through
- if !u.MustChangePassword {
- ctx.ServerError("MustUpdatePassword", errors.New("cannot update password.. Please visit the settings page"))
+ if !ctx.Doer.MustChangePassword {
+ ctx.ServerError("MustUpdatePassword", errors.New("cannot update password. Please visit the settings page"))
return
}
@@ -298,44 +283,34 @@ func MustChangePasswordPost(ctx *context.Context) {
return
}
- if len(form.Password) < setting.MinPasswordLength {
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form)
- return
+ opts := &user_service.UpdateAuthOptions{
+ Password: optional.Some(form.Password),
+ MustChangePassword: optional.Some(false),
}
-
- if !password.IsComplexEnough(form.Password) {
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form)
- return
- }
- pwned, err := password.IsPwned(ctx, form.Password)
- if pwned {
- ctx.Data["Err_Password"] = true
- errMsg := ctx.Tr("auth.password_pwned")
- if err != nil {
- log.Error(err.Error())
- errMsg = ctx.Tr("auth.password_pwned_err")
+ if err := user_service.UpdateAuth(ctx, ctx.Doer, opts); err != nil {
+ switch {
+ case errors.Is(err, password.ErrMinLength):
+ ctx.Data["Err_Password"] = true
+ ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form)
+ case errors.Is(err, password.ErrComplexity):
+ ctx.Data["Err_Password"] = true
+ ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form)
+ case errors.Is(err, password.ErrIsPwned):
+ ctx.Data["Err_Password"] = true
+ ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplMustChangePassword, &form)
+ case password.IsErrIsPwnedRequest(err):
+ log.Error("%s", err.Error())
+ ctx.Data["Err_Password"] = true
+ ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form)
+ default:
+ ctx.ServerError("UpdateAuth", err)
}
- ctx.RenderWithErr(errMsg, tplMustChangePassword, &form)
- return
- }
-
- if err = u.SetPassword(form.Password); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
-
- u.MustChangePassword = false
-
- if err := user_model.UpdateUserCols(ctx, u, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil {
- ctx.ServerError("UpdateUser", err)
return
}
ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
- log.Trace("User updated password: %s", u.Name)
+ log.Trace("User updated password: %s", ctx.Doer.Name)
if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) {
middleware.DeleteRedirectToCookie(ctx.Resp)
diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go
index 95c8d262a53..1079f44a085 100644
--- a/routers/web/auth/webauthn.go
+++ b/routers/web/auth/webauthn.go
@@ -11,9 +11,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
wa "code.gitea.io/gitea/modules/auth/webauthn"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
"github.com/go-webauthn/webauthn/protocol"
diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go
index 525ca9be53c..dd20663f94b 100644
--- a/routers/web/devtest/devtest.go
+++ b/routers/web/devtest/devtest.go
@@ -10,8 +10,8 @@ import (
"time"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/services/context"
)
// List all devtest templates, they will be used for e2e tests for the UI components
diff --git a/routers/web/events/events.go b/routers/web/events/events.go
index 1a5a162c1a2..52f20e07dc3 100644
--- a/routers/web/events/events.go
+++ b/routers/web/events/events.go
@@ -7,11 +7,11 @@ import (
"net/http"
"time"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/web/auth"
+ "code.gitea.io/gitea/services/context"
)
// Events listens for events
diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go
index d81884ec62d..2cde8b655ee 100644
--- a/routers/web/explore/code.go
+++ b/routers/web/explore/code.go
@@ -8,9 +8,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go
index dc1318beefe..f8fd6ec38ef 100644
--- a/routers/web/explore/org.go
+++ b/routers/web/explore/org.go
@@ -6,9 +6,10 @@ package explore
import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
)
// Organizations render explore organizations page
@@ -24,8 +25,16 @@ func Organizations(ctx *context.Context) {
visibleTypes = append(visibleTypes, structs.VisibleTypeLimited, structs.VisibleTypePrivate)
}
- if ctx.FormString("sort") == "" {
- ctx.SetFormString("sort", setting.UI.ExploreDefaultSort)
+ supportedSortOrders := container.SetOf(
+ "newest",
+ "oldest",
+ "alphabetically",
+ "reversealphabetically",
+ )
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = "newest"
+ ctx.SetFormString("sort", sortOrder)
}
RenderUserSearch(ctx, &user_model.SearchUserOptions{
@@ -33,5 +42,7 @@ func Organizations(ctx *context.Context) {
Type: user_model.UserTypeOrganization,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
Visible: visibleTypes,
+
+ SupportedSortOrders: supportedSortOrders,
}, tplExploreUsers)
}
diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go
index 737e34f0b33..b390490e2ff 100644
--- a/routers/web/explore/repo.go
+++ b/routers/web/explore/repo.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/explore/topic.go b/routers/web/explore/topic.go
index bb1be310de7..95fecfe2b81 100644
--- a/routers/web/explore/topic.go
+++ b/routers/web/explore/topic.go
@@ -8,8 +8,8 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
)
diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go
index 09d31f95ef4..41f440f9d96 100644
--- a/routers/web/explore/user.go
+++ b/routers/web/explore/user.go
@@ -10,12 +10,13 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -79,10 +80,16 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
fallthrough
default:
// in case the sortType is not valid, we set it to recentupdate
+ sortOrder = "recentupdate"
ctx.Data["SortType"] = "recentupdate"
orderBy = "`user`.updated_unix DESC"
}
+ if opts.SupportedSortOrders != nil && !opts.SupportedSortOrders.Contains(sortOrder) {
+ ctx.NotFound("unsupported sort order", nil)
+ return
+ }
+
opts.Keyword = ctx.FormTrim("q")
opts.OrderBy = orderBy
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
@@ -132,8 +139,16 @@ func Users(ctx *context.Context) {
ctx.Data["PageIsExploreUsers"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
- if ctx.FormString("sort") == "" {
- ctx.SetFormString("sort", setting.UI.ExploreDefaultSort)
+ supportedSortOrders := container.SetOf(
+ "newest",
+ "oldest",
+ "alphabetically",
+ "reversealphabetically",
+ )
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = "newest"
+ ctx.SetFormString("sort", sortOrder)
}
RenderUserSearch(ctx, &user_model.SearchUserOptions{
@@ -142,5 +157,7 @@ func Users(ctx *context.Context) {
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
IsActive: util.OptionalBoolTrue,
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
+
+ SupportedSortOrders: supportedSortOrders,
}, tplExploreUsers)
}
diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go
index f13038ff9b4..80ce2ad198b 100644
--- a/routers/web/feed/branch.go
+++ b/routers/web/feed/branch.go
@@ -9,7 +9,7 @@ import (
"time"
"code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index 4d4918a8fd8..298fe0bb39a 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -6,6 +6,7 @@ package feed
import (
"fmt"
"html"
+ "html/template"
"net/http"
"net/url"
"strconv"
@@ -13,12 +14,12 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
@@ -51,9 +52,11 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
// If rendering fails, the original markdown text is returned
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) string {
markdownCtx := &markup.RenderContext{
- Ctx: ctx,
- URLPrefix: act.GetRepoLink(ctx),
- Type: markdown.MarkupName,
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: act.GetRepoLink(ctx),
+ },
+ Type: markdown.MarkupName,
Metas: map[string]string{
"user": act.GetRepoUserName(ctx),
"repo": act.GetRepoName(ctx),
@@ -77,119 +80,120 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
// title
title = act.ActUser.DisplayName() + " "
+ var titleExtra template.HTML
switch act.OpType {
case activities_model.ActionCreateRepo:
- title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_repo", act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
link.Href = act.GetRepoAbsoluteLink(ctx)
case activities_model.ActionRenameRepo:
- title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
link.Href = act.GetRepoAbsoluteLink(ctx)
case activities_model.ActionCommitRepo:
link.Href = toBranchLink(ctx, act)
if len(act.Content) != 0 {
- title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.commit_repo", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
} else {
- title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_branch", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx))
}
case activities_model.ActionCreateIssue:
link.Href = toIssueLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionCreatePullRequest:
link.Href = toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionTransferRepo:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx))
case activities_model.ActionPushTag:
link.Href = toTagLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetTag(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.push_tag", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetTag(), act.ShortRepoPath(ctx))
case activities_model.ActionCommentIssue:
issueLink := toIssueLink(ctx, act)
if link.Href == "#" {
link.Href = issueLink
}
- title += ctx.TrHTMLEscapeArgs("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionMergePullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionAutoMergePullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionCloseIssue:
issueLink := toIssueLink(ctx, act)
if link.Href == "#" {
link.Href = issueLink
}
- title += ctx.TrHTMLEscapeArgs("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionReopenIssue:
issueLink := toIssueLink(ctx, act)
if link.Href == "#" {
link.Href = issueLink
}
- title += ctx.TrHTMLEscapeArgs("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionClosePullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionReopenPullRequest:
pullLink := toPullLink(ctx, act)
if link.Href == "#" {
link.Href = pullLink
}
- title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionDeleteTag:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoAbsoluteLink(ctx), act.GetTag(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.delete_tag", act.GetRepoAbsoluteLink(ctx), act.GetTag(), act.ShortRepoPath(ctx))
case activities_model.ActionDeleteBranch:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoAbsoluteLink(ctx), html.EscapeString(act.GetBranch()), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.delete_branch", act.GetRepoAbsoluteLink(ctx), html.EscapeString(act.GetBranch()), act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncPush:
srcLink := toSrcLink(ctx, act)
if link.Href == "#" {
link.Href = srcLink
}
- title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncCreate:
srcLink := toSrcLink(ctx, act)
if link.Href == "#" {
link.Href = srcLink
}
- title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx))
case activities_model.ActionMirrorSyncDelete:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx))
case activities_model.ActionApprovePullRequest:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionRejectPullRequest:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionCommentPull:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx))
case activities_model.ActionPublishRelease:
releaseLink := toReleaseLink(ctx, act)
if link.Href == "#" {
link.Href = releaseLink
}
- title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoAbsoluteLink(ctx), releaseLink, act.ShortRepoPath(ctx), act.Content)
+ titleExtra = ctx.Locale.Tr("action.publish_release", act.GetRepoAbsoluteLink(ctx), releaseLink, act.ShortRepoPath(ctx), act.Content)
case activities_model.ActionPullReviewDismissed:
pullLink := toPullLink(ctx, act)
- title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx), act.GetIssueInfos()[1])
+ titleExtra = ctx.Locale.Tr("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx), act.GetIssueInfos()[1])
case activities_model.ActionStarRepo:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.starred_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
case activities_model.ActionWatchRepo:
link.Href = act.GetRepoAbsoluteLink(ctx)
- title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
+ titleExtra = ctx.Locale.Tr("action.watched_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx))
default:
return nil, fmt.Errorf("unknown action type: %v", act.OpType)
}
@@ -199,7 +203,6 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
switch act.OpType {
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
push := templates.ActionContent2Commits(act)
- repoLink := act.GetRepoAbsoluteLink(ctx)
for _, commit := range push.Commits {
if len(desc) != 0 {
@@ -208,7 +211,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
desc += fmt.Sprintf("%s\n%s",
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
commit.Sha1,
- templates.RenderCommitMessage(ctx, commit.Message, repoLink, nil),
+ templates.RenderCommitMessage(ctx, commit.Message, nil),
)
}
@@ -232,7 +235,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
case activities_model.ActionCloseIssue, activities_model.ActionReopenIssue, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest:
desc = act.GetIssueTitle(ctx)
case activities_model.ActionPullReviewDismissed:
- desc = ctx.Tr("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2]
+ desc = ctx.Locale.TrString("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2]
}
}
if len(content) == 0 {
@@ -240,9 +243,10 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
}
items = append(items, &feeds.Item{
- Title: title,
+ Title: template.HTMLEscapeString(title) + string(titleExtra),
Link: link,
Description: desc,
+ IsPermaLink: "false",
Author: &feeds.Author{
Name: act.ActUser.DisplayName(),
Email: act.ActUser.GetEmail(),
@@ -288,11 +292,12 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, i
link := &feeds.Link{Href: rel.HTMLURL()}
content, err = markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: rel.Repo.Link(),
- Metas: rel.Repo.ComposeMetas(ctx),
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: rel.Repo.Link(),
+ },
+ Metas: rel.Repo.ComposeMetas(ctx),
}, rel.Note)
-
if err != nil {
return nil, err
}
diff --git a/routers/web/feed/file.go b/routers/web/feed/file.go
index 56a9c54ddcb..1ab768ff27f 100644
--- a/routers/web/feed/file.go
+++ b/routers/web/feed/file.go
@@ -9,9 +9,9 @@ import (
"time"
"code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go
index ce86727e248..2b70aad17b7 100644
--- a/routers/web/feed/profile.go
+++ b/routers/web/feed/profile.go
@@ -7,9 +7,9 @@ import (
"time"
activities_model "code.gitea.io/gitea/models/activities"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
@@ -42,8 +42,10 @@ func showUserFeed(ctx *context.Context, formatType string) {
}
ctxUserDescription, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: ctx.ContextUser.HTMLURL(),
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.ContextUser.HTMLURL(),
+ },
Metas: map[string]string{
"user": ctx.ContextUser.GetDisplayName(),
},
@@ -54,7 +56,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
}
feed := &feeds.Feed{
- Title: ctx.Tr("home.feed_of", ctx.ContextUser.DisplayName()),
+ Title: ctx.Locale.TrString("home.feed_of", ctx.ContextUser.DisplayName()),
Link: &feeds.Link{Href: ctx.ContextUser.HTMLURL()},
Description: ctxUserDescription,
Created: time.Now(),
diff --git a/routers/web/feed/release.go b/routers/web/feed/release.go
index fbfa11c63ec..273f47e3b46 100644
--- a/routers/web/feed/release.go
+++ b/routers/web/feed/release.go
@@ -6,16 +6,18 @@ package feed
import (
"time"
+ "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
// shows tags and/or releases on the repo as RSS / Atom feed
func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleasesOnly bool, formatType string) {
- releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{
+ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeTags: !isReleasesOnly,
+ RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
ctx.ServerError("GetReleasesByRepoID", err)
@@ -26,10 +28,10 @@ func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleas
var link *feeds.Link
if isReleasesOnly {
- title = ctx.Tr("repo.release.releases_for", repo.FullName())
+ title = ctx.Locale.TrString("repo.release.releases_for", repo.FullName())
link = &feeds.Link{Href: repo.HTMLURL() + "/release"}
} else {
- title = ctx.Tr("repo.release.tags_for", repo.FullName())
+ title = ctx.Locale.TrString("repo.release.tags_for", repo.FullName())
link = &feeds.Link{Href: repo.HTMLURL() + "/tags"}
}
diff --git a/routers/web/feed/render.go b/routers/web/feed/render.go
index 8931dae8cce..a41808c24ac 100644
--- a/routers/web/feed/render.go
+++ b/routers/web/feed/render.go
@@ -4,7 +4,7 @@
package feed
import (
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// RenderBranchFeed render format for branch or file
diff --git a/routers/web/feed/repo.go b/routers/web/feed/repo.go
index 5fcad267791..bfcc3a37d6a 100644
--- a/routers/web/feed/repo.go
+++ b/routers/web/feed/repo.go
@@ -8,7 +8,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
"github.com/gorilla/feeds"
)
@@ -27,7 +27,7 @@ func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType
}
feed := &feeds.Feed{
- Title: ctx.Tr("home.feed_of", repo.FullName()),
+ Title: ctx.Locale.TrString("home.feed_of", repo.FullName()),
Link: &feeds.Link{Href: repo.HTMLURL()},
Description: repo.Description,
Created: time.Now(),
diff --git a/routers/web/githttp.go b/routers/web/githttp.go
index b2fb5b472f7..5f1dedce763 100644
--- a/routers/web/githttp.go
+++ b/routers/web/githttp.go
@@ -6,11 +6,10 @@ package web
import (
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
- context_service "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context"
)
func requireSignIn(ctx *context.Context) {
@@ -28,16 +27,16 @@ func requireSignIn(ctx *context.Context) {
func gitHTTPRouters(m *web.Route) {
m.Group("", func() {
- m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
- m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
- m.GetOptions("/info/refs", repo.GetInfoRefs)
- m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
- m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
- m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
- m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
- m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
- m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
- m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
- m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
- }, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
+ m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
+ m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
+ m.Methods("GET,OPTIONS", "/info/refs", repo.GetInfoRefs)
+ m.Methods("GET,OPTIONS", "/HEAD", repo.GetTextFile("HEAD"))
+ m.Methods("GET,OPTIONS", "/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
+ m.Methods("GET,OPTIONS", "/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
+ m.Methods("GET,OPTIONS", "/objects/info/packs", repo.GetInfoPacks)
+ m.Methods("GET,OPTIONS", "/objects/info/{file:[^/]*}", repo.GetTextFile(""))
+ m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
+ m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
+ m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
+ }, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
}
diff --git a/routers/web/goget.go b/routers/web/goget.go
index c5b8b6cbc01..8d5612ebfe9 100644
--- a/routers/web/goget.go
+++ b/routers/web/goget.go
@@ -12,9 +12,9 @@ import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
func goGet(ctx *context.Context) {
diff --git a/routers/web/healthcheck/check.go b/routers/web/healthcheck/check.go
index ecb73a928fd..85f47613f0d 100644
--- a/routers/web/healthcheck/check.go
+++ b/routers/web/healthcheck/check.go
@@ -121,10 +121,6 @@ func checkDatabase(ctx context.Context, checks checks) status {
// cache checks gitea cache status
func checkCache(checks checks) status {
- if !setting.CacheService.Enabled {
- return pass
- }
-
st := componentStatus{}
if err := cache.GetCache().Ping(); err != nil {
st.Status = fail
diff --git a/routers/web/home.go b/routers/web/home.go
index 2321b00efe7..555f94c9835 100644
--- a/routers/web/home.go
+++ b/routers/web/home.go
@@ -12,7 +12,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
@@ -21,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/web/auth"
"code.gitea.io/gitea/routers/web/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/misc/markup.go b/routers/web/misc/markup.go
index c91da9a7f10..2dbbd6fc097 100644
--- a/routers/web/misc/markup.go
+++ b/routers/web/misc/markup.go
@@ -5,10 +5,10 @@
package misc
import (
- "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
)
// Markup render markup document to HTML
diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go
index e3519940107..ac5496ce91a 100644
--- a/routers/web/misc/misc.go
+++ b/routers/web/misc/misc.go
@@ -15,7 +15,7 @@ import (
)
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
- if !git.SupportProcReceive {
+ if !git.DefaultFeatures.SupportProcReceive {
rw.WriteHeader(http.StatusNotFound)
return
}
@@ -33,10 +33,6 @@ func DummyOK(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
}
-func DummyBadRequest(w http.ResponseWriter, req *http.Request) {
- w.WriteHeader(http.StatusBadRequest)
-}
-
func RobotsTxt(w http.ResponseWriter, req *http.Request) {
robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt")
if ok, _ := util.IsExist(robotsTxt); !ok {
diff --git a/routers/web/misc/swagger.go b/routers/web/misc/swagger.go
index 72c09a37801..5fddfa8885f 100644
--- a/routers/web/misc/swagger.go
+++ b/routers/web/misc/swagger.go
@@ -7,7 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
// tplSwagger swagger page template
diff --git a/routers/web/nodeinfo.go b/routers/web/nodeinfo.go
index 01b71e7086b..f1cc7bf530f 100644
--- a/routers/web/nodeinfo.go
+++ b/routers/web/nodeinfo.go
@@ -7,8 +7,8 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
type nodeInfoLinks struct {
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index a9adfdc03cd..4a7378689ad 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -5,20 +5,21 @@ package org
import (
"net/http"
+ "path"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -44,19 +45,6 @@ func Home(ctx *context.Context) {
ctx.Data["PageIsUserProfile"] = true
ctx.Data["Title"] = org.DisplayName()
- if len(org.Description) != 0 {
- desc, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: ctx.Repo.RepoLink,
- Metas: map[string]string{"mode": "document"},
- GitRepo: ctx.Repo.GitRepo,
- }, org.Description)
- if err != nil {
- ctx.ServerError("RenderString", err)
- return
- }
- ctx.Data["RenderedDescription"] = desc
- }
var orderBy db.SearchOrderBy
ctx.Data["SortType"] = ctx.FormString("sort")
@@ -131,18 +119,12 @@ func Home(ctx *context.Context) {
return
}
- var isFollowing bool
- if ctx.Doer != nil {
- isFollowing = user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
- }
-
ctx.Data["Repos"] = repos
ctx.Data["Total"] = count
ctx.Data["Members"] = members
ctx.Data["Teams"] = ctx.Org.Teams
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
ctx.Data["PageIsViewRepositories"] = true
- ctx.Data["IsFollowing"] = isFollowing
err = shared_user.LoadHeaderCount(ctx)
if err != nil {
@@ -157,14 +139,14 @@ func Home(ctx *context.Context) {
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
- profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
+ profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
defer profileClose()
- prepareOrgProfileReadme(ctx, profileGitRepo, profileReadmeBlob)
+ prepareOrgProfileReadme(ctx, profileGitRepo, profileDbRepo, profileReadmeBlob)
ctx.HTML(http.StatusOK, tplOrgHome)
}
-func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileReadme *git.Blob) {
+func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
if profileGitRepo == nil || profileReadme == nil {
return
}
@@ -175,7 +157,13 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
if profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
- Metas: map[string]string{"mode": "document"},
+ Links: markup.Links{
+ // Pass repo link to markdown render for the full link of media elements.
+ // The profile of default branch would be shown.
+ Base: profileDbRepo.Link(),
+ BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
+ },
+ Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
diff --git a/routers/web/org/members.go b/routers/web/org/members.go
index 15a615c706f..9a3d60e122b 100644
--- a/routers/web/org/members.go
+++ b/routers/web/org/members.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/org/org.go b/routers/web/org/org.go
index 52f8df8a1c7..f94dd16eaef 100644
--- a/routers/web/org/org.go
+++ b/routers/web/org/org.go
@@ -12,10 +12,10 @@ import (
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
@@ -29,7 +29,7 @@ func Create(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_org")
ctx.Data["DefaultOrgVisibilityMode"] = setting.Service.DefaultOrgVisibilityMode
if !ctx.Doer.CanCreateOrganization() {
- ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed")))
+ ctx.ServerError("Not allowed", errors.New(ctx.Locale.TrString("org.form.create_org_not_allowed")))
return
}
ctx.HTML(http.StatusOK, tplCreateOrg)
@@ -41,7 +41,7 @@ func CreatePost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_org")
if !ctx.Doer.CanCreateOrganization() {
- ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed")))
+ ctx.ServerError("Not allowed", errors.New(ctx.Locale.TrString("org.form.create_org_not_allowed")))
return
}
diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go
index f78bd002742..02eae8052ea 100644
--- a/routers/web/org/org_labels.go
+++ b/routers/web/org/org_labels.go
@@ -8,10 +8,10 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 03798a712c2..338558fa239 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -17,12 +17,12 @@ import (
attachment_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
@@ -353,7 +353,7 @@ func ViewProject(ctx *context.Context) {
}
if boards[0].ID == 0 {
- boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
+ boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
@@ -377,16 +377,16 @@ func ViewProject(ctx *context.Context) {
linkedPrsMap := make(map[int64][]*issues_model.Issue)
for _, issuesList := range issuesMap {
for _, issue := range issuesList {
- var referencedIds []int64
+ var referencedIDs []int64
for _, comment := range issue.Comments {
if comment.RefIssueID != 0 && comment.RefIsPull {
- referencedIds = append(referencedIds, comment.RefIssueID)
+ referencedIDs = append(referencedIDs, comment.RefIssueID)
}
}
- if len(referencedIds) > 0 {
+ if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
- IssueIDs: referencedIds,
+ IssueIDs: referencedIDs,
IsPull: util.OptionalBoolTrue,
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
@@ -679,7 +679,7 @@ func MoveIssues(ctx *context.Context) {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
- Title: ctx.Tr("repo.projects.type.uncategorized"),
+ Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
diff --git a/routers/web/org/projects_test.go b/routers/web/org/projects_test.go
index 8053ab4cf93..f4ccfe1c066 100644
--- a/routers/web/org/projects_test.go
+++ b/routers/web/org/projects_test.go
@@ -7,8 +7,8 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/routers/web/org"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index f0d9259d3fe..494ada4323a 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -7,7 +7,6 @@ package org
import (
"net/http"
"net/url"
- "strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
@@ -15,13 +14,14 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
@@ -71,53 +71,50 @@ func SettingsPost(ctx *context.Context) {
}
org := ctx.Org.Organization
- nameChanged := org.Name != form.Name
- // Check if organization name has been changed.
- if nameChanged {
- err := user_service.RenameUser(ctx, org.AsUser(), form.Name)
- switch {
- case user_model.IsErrUserAlreadyExist(err):
- ctx.Data["OrgName"] = true
- ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
- return
- case db.IsErrNameReserved(err):
- ctx.Data["OrgName"] = true
- ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
- return
- case db.IsErrNamePatternNotAllowed(err):
- ctx.Data["OrgName"] = true
- ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
- return
- case err != nil:
- ctx.ServerError("org_service.RenameOrganization", err)
+ if org.Name != form.Name {
+ if err := user_service.RenameUser(ctx, org.AsUser(), form.Name); err != nil {
+ if user_model.IsErrUserAlreadyExist(err) {
+ ctx.Data["Err_Name"] = true
+ ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
+ } else if db.IsErrNameReserved(err) {
+ ctx.Data["Err_Name"] = true
+ ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
+ } else if db.IsErrNamePatternNotAllowed(err) {
+ ctx.Data["Err_Name"] = true
+ ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
+ } else {
+ ctx.ServerError("RenameUser", err)
+ }
return
}
- // reset ctx.org.OrgLink with new name
- ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name)
- log.Trace("Organization name changed: %s -> %s", org.Name, form.Name)
+ ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(org.Name)
}
- // In case it's just a case change.
- org.Name = form.Name
- org.LowerName = strings.ToLower(form.Name)
+ if form.Email != "" {
+ if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
+ ctx.Data["Err_Email"] = true
+ ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsOptions, &form)
+ return
+ }
+ }
+ opts := &user_service.UpdateOptions{
+ FullName: optional.Some(form.FullName),
+ Description: optional.Some(form.Description),
+ Website: optional.Some(form.Website),
+ Location: optional.Some(form.Location),
+ Visibility: optional.Some(form.Visibility),
+ RepoAdminChangeTeamAccess: optional.Some(form.RepoAdminChangeTeamAccess),
+ }
if ctx.Doer.IsAdmin {
- org.MaxRepoCreation = form.MaxRepoCreation
+ opts.MaxRepoCreation = optional.Some(form.MaxRepoCreation)
}
- org.FullName = form.FullName
- org.Email = form.Email
- org.Description = form.Description
- org.Website = form.Website
- org.Location = form.Location
- org.RepoAdminChangeTeamAccess = form.RepoAdminChangeTeamAccess
+ visibilityChanged := org.Visibility != form.Visibility
- visibilityChanged := form.Visibility != org.Visibility
- org.Visibility = form.Visibility
-
- if err := user_model.UpdateUser(ctx, org.AsUser(), false); err != nil {
+ if err := user_service.UpdateUser(ctx, org.AsUser(), opts); err != nil {
ctx.ServerError("UpdateUser", err)
return
}
diff --git a/routers/web/org/setting/runners.go b/routers/web/org/setting/runners.go
index c3c771036aa..fe05709237d 100644
--- a/routers/web/org/setting/runners.go
+++ b/routers/web/org/setting/runners.go
@@ -4,7 +4,7 @@
package setting
import (
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
func RedirectToDefaultSetting(ctx *context.Context) {
diff --git a/routers/web/org/setting_oauth2.go b/routers/web/org/setting_oauth2.go
index ca4fe09f380..7f855795d36 100644
--- a/routers/web/org/setting_oauth2.go
+++ b/routers/web/org/setting_oauth2.go
@@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/org/setting_packages.go b/routers/web/org/setting_packages.go
index 796829d34ef..af9836e42c2 100644
--- a/routers/web/org/setting_packages.go
+++ b/routers/web/org/setting_packages.go
@@ -8,10 +8,10 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/packages"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index 9e65c8ba9cd..fd7486cacdb 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -20,12 +20,11 @@ import (
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/routers/utils"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
org_service "code.gitea.io/gitea/services/org"
@@ -127,7 +126,7 @@ func TeamsAction(ctx *context.Context) {
ctx.Error(http.StatusNotFound)
return
}
- uname := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("uname")))
+ uname := strings.ToLower(ctx.FormString("uname"))
var u *user_model.User
u, err = user_model.GetUserByName(ctx, uname)
if err != nil {
diff --git a/routers/web/passkey.go b/routers/web/passkey.go
new file mode 100644
index 00000000000..0d10a69dfe6
--- /dev/null
+++ b/routers/web/passkey.go
@@ -0,0 +1,24 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package web
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
+)
+
+type passkeyEndpointsType struct {
+ Enroll string `json:"enroll"`
+ Manage string `json:"manage"`
+}
+
+func passkeyEndpoints(ctx *context.Context) {
+ url := setting.AppURL + "user/settings/security"
+ ctx.JSON(http.StatusOK, passkeyEndpointsType{
+ Enroll: url,
+ Manage: url,
+ })
+}
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index 3b10f0b9571..e7849123779 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -15,10 +15,11 @@ import (
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/repo"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
"github.com/nektos/act/pkg/model"
@@ -60,23 +61,24 @@ func List(ctx *context.Context) {
var workflows []Workflow
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("IsEmpty", err)
return
} else if !empty {
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetBranchCommit", err)
return
}
entries, err := actions.ListWorkflows(commit)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("ListWorkflows", err)
return
}
// Get all runner labels
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID,
+ IsOnline: util.OptionalBoolTrue,
WithAvailable: true,
})
if err != nil {
@@ -93,12 +95,12 @@ func List(ctx *context.Context) {
workflow := Workflow{Entry: *entry}
content, err := actions.GetContentFromEntry(entry)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetContentFromEntry", err)
return
}
wf, err := model.ReadWorkflow(bytes.NewReader(content))
if err != nil {
- workflow.ErrMsg = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", err.Error())
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
workflows = append(workflows, workflow)
continue
}
@@ -113,7 +115,7 @@ func List(ctx *context.Context) {
continue
}
if !allRunnerLabels.Contains(ro) {
- workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_runner_helper", ro)
+ workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro)
break
}
}
@@ -170,7 +172,7 @@ func List(ctx *context.Context) {
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("FindAndCount", err)
return
}
@@ -179,7 +181,7 @@ func List(ctx *context.Context) {
}
if err := actions_model.RunList(runs).LoadTriggerUser(ctx); err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("LoadTriggerUser", err)
return
}
@@ -187,7 +189,7 @@ func List(ctx *context.Context) {
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
if err != nil {
- ctx.Error(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetActors", err)
return
}
ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors)
@@ -200,6 +202,7 @@ func List(ctx *context.Context) {
pager.AddParamString("actor", fmt.Sprint(actorID))
pager.AddParamString("status", fmt.Sprint(status))
ctx.Data["Page"] = pager
+ ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
ctx.HTML(http.StatusOK, tplListActions)
}
diff --git a/routers/web/repo/actions/badge.go b/routers/web/repo/actions/badge.go
new file mode 100644
index 00000000000..6fa951826c3
--- /dev/null
+++ b/routers/web/repo/actions/badge.go
@@ -0,0 +1,56 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "path/filepath"
+ "strings"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/modules/badge"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+)
+
+func GetWorkflowBadge(ctx *context.Context) {
+ workflowFile := ctx.Params("workflow_name")
+ branch := ctx.Req.URL.Query().Get("branch")
+ if branch == "" {
+ branch = ctx.Repo.Repository.DefaultBranch
+ }
+ branchRef := fmt.Sprintf("refs/heads/%s", branch)
+ event := ctx.Req.URL.Query().Get("event")
+
+ badge, err := getWorkflowBadge(ctx, workflowFile, branchRef, event)
+ if err != nil {
+ ctx.ServerError("GetWorkflowBadge", err)
+ return
+ }
+
+ ctx.Data["Badge"] = badge
+ ctx.RespHeader().Set("Content-Type", "image/svg+xml")
+ ctx.HTML(http.StatusOK, "shared/actions/runner_badge")
+}
+
+func getWorkflowBadge(ctx *context.Context, workflowFile, branchName, event string) (badge.Badge, error) {
+ extension := filepath.Ext(workflowFile)
+ workflowName := strings.TrimSuffix(workflowFile, extension)
+
+ run, err := actions_model.GetWorkflowLatestRun(ctx, ctx.Repo.Repository.ID, workflowFile, branchName, event)
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ return badge.GenerateBadge(workflowName, "no status", badge.DefaultColor), nil
+ }
+ return badge.Badge{}, err
+ }
+
+ color, ok := badge.StatusColorMap[run.Status]
+ if !ok {
+ return badge.GenerateBadge(workflowName, "unknown status", badge.DefaultColor), nil
+ }
+ return badge.GenerateBadge(workflowName, run.Status.String(), color), nil
+}
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 1cdae32a32f..52c3cf1d07c 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -12,6 +12,7 @@ import (
"io"
"net/http"
"net/url"
+ "strconv"
"strings"
"time"
@@ -21,12 +22,12 @@ import (
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
- context_module "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
actions_service "code.gitea.io/gitea/services/actions"
+ context_module "code.gitea.io/gitea/services/context"
"xorm.io/builder"
)
@@ -57,15 +58,16 @@ type ViewRequest struct {
type ViewResponse struct {
State struct {
Run struct {
- Link string `json:"link"`
- Title string `json:"title"`
- Status string `json:"status"`
- CanCancel bool `json:"canCancel"`
- CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
- CanRerun bool `json:"canRerun"`
- Done bool `json:"done"`
- Jobs []*ViewJob `json:"jobs"`
- Commit ViewCommit `json:"commit"`
+ Link string `json:"link"`
+ Title string `json:"title"`
+ Status string `json:"status"`
+ CanCancel bool `json:"canCancel"`
+ CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
+ CanRerun bool `json:"canRerun"`
+ CanDeleteArtifact bool `json:"canDeleteArtifact"`
+ Done bool `json:"done"`
+ Jobs []*ViewJob `json:"jobs"`
+ Commit ViewCommit `json:"commit"`
} `json:"run"`
CurrentJob struct {
Title string `json:"title"`
@@ -146,6 +148,7 @@ func ViewPost(ctx *context_module.Context) {
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
+ resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.Done = run.Status.IsDone()
resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json
resp.State.Run.Status = run.Status.String()
@@ -168,8 +171,8 @@ func ViewPost(ctx *context_module.Context) {
Link: run.RefLink(),
}
resp.State.Run.Commit = ViewCommit{
- LocaleCommit: ctx.Tr("actions.runs.commit"),
- LocalePushedBy: ctx.Tr("actions.runs.pushed_by"),
+ LocaleCommit: ctx.Locale.TrString("actions.runs.commit"),
+ LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"),
ShortSha: base.ShortSha(run.CommitSHA),
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
Pusher: pusher,
@@ -194,7 +197,7 @@ func ViewPost(ctx *context_module.Context) {
resp.State.CurrentJob.Title = current.Name
resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale)
if run.NeedApproval {
- resp.State.CurrentJob.Detail = ctx.Locale.Tr("actions.need_approval_desc")
+ resp.State.CurrentJob.Detail = ctx.Locale.TrString("actions.need_approval_desc")
}
resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead fo 'null' in json
resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json
@@ -260,10 +263,14 @@ func ViewPost(ctx *context_module.Context) {
}
// Rerun will rerun jobs in the given run
-// jobIndex = 0 means rerun all jobs
+// If jobIndexStr is a blank string, it means rerun all jobs
func Rerun(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")
- jobIndex := ctx.ParamsInt64("job")
+ jobIndexStr := ctx.Params("job")
+ var jobIndex int64
+ if jobIndexStr != "" {
+ jobIndex, _ = strconv.ParseInt(jobIndexStr, 10, 64)
+ }
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
@@ -279,12 +286,23 @@ func Rerun(ctx *context_module.Context) {
return
}
+ // reset run's start and stop time when it is done
+ if run.Status.IsDone() {
+ run.PreviousDuration = run.Duration()
+ run.Started = 0
+ run.Stopped = 0
+ if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "previous_duration"); err != nil {
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ }
+
job, jobs := getRunJobs(ctx, runIndex, jobIndex)
if ctx.Written() {
return
}
- if jobIndex != 0 {
+ if jobIndexStr != "" {
jobs = []*actions_model.ActionRunJob{job}
}
@@ -524,6 +542,29 @@ func ArtifactsView(ctx *context_module.Context) {
ctx.JSON(http.StatusOK, artifactsResponse)
}
+func ArtifactsDeleteView(ctx *context_module.Context) {
+ if !ctx.Repo.CanWrite(unit.TypeActions) {
+ ctx.Error(http.StatusForbidden, "no permission")
+ return
+ }
+
+ runIndex := ctx.ParamsInt64("run")
+ artifactName := ctx.Params("artifact_name")
+
+ run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
+ if err != nil {
+ ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
+ return errors.Is(err, util.ErrNotExist)
+ }, err)
+ return
+ }
+ if err = actions_model.SetArtifactNeedDelete(ctx, run.ID, artifactName); err != nil {
+ ctx.Error(http.StatusInternalServerError, err.Error())
+ return
+ }
+ ctx.JSON(http.StatusOK, struct{}{})
+}
+
func ArtifactsDownloadView(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")
artifactName := ctx.Params("artifact_name")
@@ -551,6 +592,14 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
return
}
+ // if artifacts status is not uploaded-confirmed, treat it as not found
+ for _, art := range artifacts {
+ if art.Status != int64(actions_model.ArtifactStatusUploadConfirmed) {
+ ctx.Error(http.StatusNotFound, "artifact not found")
+ return
+ }
+ }
+
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(artifactName), artifactName))
writer := zip.NewWriter(ctx.Resp)
diff --git a/routers/web/repo/activity.go b/routers/web/repo/activity.go
index 3d030edaca0..6f6641cc65e 100644
--- a/routers/web/repo/activity.go
+++ b/routers/web/repo/activity.go
@@ -10,7 +10,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
const (
@@ -22,6 +22,8 @@ func Activity(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.activity")
ctx.Data["PageIsActivity"] = true
+ ctx.Data["PageIsPulse"] = true
+
ctx.Data["Period"] = ctx.Params("period")
timeUntil := time.Now()
diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go
index 8c322b45e5e..f0c5622aec7 100644
--- a/routers/web/repo/attachment.go
+++ b/routers/web/repo/attachment.go
@@ -9,15 +9,15 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/attachment"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
repo_service "code.gitea.io/gitea/services/repository"
)
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index 52d350ff665..b088b8387e7 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -13,13 +13,14 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
type blameRow struct {
@@ -125,13 +126,15 @@ func RefBlame(ctx *context.Context) {
}
type blameResult struct {
- Parts []git.BlamePart
+ Parts []*git.BlamePart
UsesIgnoreRevs bool
FaultyIgnoreRevsFile bool
}
func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) {
- blameReader, err := git.CreateBlameReader(ctx, repoPath, commit, file, bypassBlameIgnore)
+ objectFormat := ctx.Repo.GetObjectFormat()
+
+ blameReader, err := git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, bypassBlameIgnore)
if err != nil {
return nil, err
}
@@ -147,7 +150,7 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil
if len(r.Parts) == 0 && r.UsesIgnoreRevs {
// try again without ignored revs
- blameReader, err = git.CreateBlameReader(ctx, repoPath, commit, file, true)
+ blameReader, err = git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, true)
if err != nil {
return nil, err
}
@@ -170,7 +173,9 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil
func fillBlameResult(br *git.BlameReader, r *blameResult) error {
r.UsesIgnoreRevs = br.UsesIgnoreRevs()
- r.Parts = make([]git.BlamePart, 0, 5)
+ previousHelper := make(map[string]*git.BlamePart)
+
+ r.Parts = make([]*git.BlamePart, 0, 5)
for {
blamePart, err := br.NextPart()
if err != nil {
@@ -179,13 +184,23 @@ func fillBlameResult(br *git.BlameReader, r *blameResult) error {
if blamePart == nil {
break
}
- r.Parts = append(r.Parts, *blamePart)
+
+ if prev, ok := previousHelper[blamePart.Sha]; ok {
+ if blamePart.PreviousSha == "" {
+ blamePart.PreviousSha = prev.PreviousSha
+ blamePart.PreviousPath = prev.PreviousPath
+ }
+ } else {
+ previousHelper[blamePart.Sha] = blamePart
+ }
+
+ r.Parts = append(r.Parts, blamePart)
}
return nil
}
-func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[string]*user_model.UserCommit {
+func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[string]*user_model.UserCommit {
// store commit data by SHA to look up avatar info etc
commitNames := make(map[string]*user_model.UserCommit)
// and as blameParts can reference the same commits multiple
@@ -227,34 +242,14 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[str
return commitNames
}
-func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit) {
+func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink
- language := ""
-
- indexFilename, worktree, deleteTemporaryFile, err := ctx.Repo.GitRepo.ReadTreeToTemporaryIndex(ctx.Repo.CommitID)
- if err == nil {
- defer deleteTemporaryFile()
-
- filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{
- CachedOnly: true,
- Attributes: []string{"linguist-language", "gitlab-language"},
- Filenames: []string{ctx.Repo.TreePath},
- IndexFile: indexFilename,
- WorkTree: worktree,
- })
- if err != nil {
- log.Error("Unable to load attributes for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
- }
-
- language = filename2attribute2info[ctx.Repo.TreePath]["linguist-language"]
- if language == "" || language == "unspecified" {
- language = filename2attribute2info[ctx.Repo.TreePath]["gitlab-language"]
- }
- if language == "unspecified" {
- language = ""
- }
+ language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
+ if err != nil {
+ log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
}
+
lines := make([]string, 0)
rows := make([]*blameRow, 0)
escapeStatus := &charset.EscapeStatus{}
@@ -310,8 +305,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
lexerName = lexerNameForLine
}
- br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale)
- br.Code = gotemplate.HTML(line)
+ br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale)
rows = append(rows, br)
escapeStatus = escapeStatus.Or(br.EscapeStatus)
}
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index bc72d5f2eca..05f06a3ceb7 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -16,7 +16,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
@@ -24,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
@@ -147,11 +147,18 @@ func RestoreBranchPost(ctx *context.Context) {
return
}
+ objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath())
+ if err != nil {
+ log.Error("RestoreBranch: CreateBranch: %w", err)
+ ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
+ return
+ }
+
// Don't return error below this
if err := repo_service.PushUpdate(
&repo_module.PushUpdateOptions{
RefFullName: git.RefNameFromBranch(deletedBranch.Name),
- OldCommitID: git.EmptySHA,
+ OldCommitID: objectFormat.EmptyObjectID().String(),
NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name,
@@ -191,9 +198,9 @@ func CreateBranch(ctx *context.Context) {
}
err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
} else if ctx.Repo.IsViewBranch {
- err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
+ err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.BranchName, form.NewBranchName)
} else {
- err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
+ err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName)
}
if err != nil {
if models.IsErrProtectedTagName(err) {
diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go
index 25dd8812198..088f8d889d4 100644
--- a/routers/web/repo/cherry_pick.go
+++ b/routers/web/repo/cherry_pick.go
@@ -12,11 +12,11 @@ import (
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/repository/files"
)
@@ -104,9 +104,9 @@ func CherryPickPost(ctx *context.Context) {
message := strings.TrimSpace(form.CommitSummary)
if message == "" {
if form.Revert {
- message = ctx.Tr("repo.commit.revert-header", sha)
+ message = ctx.Locale.TrString("repo.commit.revert-header", sha)
} else {
- message = ctx.Tr("repo.commit.cherry-pick-header", sha)
+ message = ctx.Locale.TrString("repo.commit.cherry-pick-header", sha)
}
}
diff --git a/routers/web/repo/code_frequency.go b/routers/web/repo/code_frequency.go
new file mode 100644
index 00000000000..c76f492da07
--- /dev/null
+++ b/routers/web/repo/code_frequency.go
@@ -0,0 +1,41 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "errors"
+ "net/http"
+
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/services/context"
+ contributors_service "code.gitea.io/gitea/services/repository"
+)
+
+const (
+ tplCodeFrequency base.TplName = "repo/activity"
+)
+
+// CodeFrequency renders the page to show repository code frequency
+func CodeFrequency(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.code_frequency")
+
+ ctx.Data["PageIsActivity"] = true
+ ctx.Data["PageIsCodeFrequency"] = true
+ ctx.PageData["repoLink"] = ctx.Repo.RepoLink
+
+ ctx.HTML(http.StatusOK, tplCodeFrequency)
+}
+
+// CodeFrequencyData returns JSON of code frequency data
+func CodeFrequencyData(ctx *context.Context) {
+ if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
+ if errors.Is(err, contributors_service.ErrAwaitGeneration) {
+ ctx.Status(http.StatusAccepted)
+ return
+ }
+ ctx.ServerError("GetCodeFrequencyData", err)
+ } else {
+ ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
+ }
+}
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 3587d287fc5..16da917d222 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -7,7 +7,9 @@ package repo
import (
"errors"
"fmt"
+ "html/template"
"net/http"
+ "path"
"strings"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -17,11 +19,14 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitgraph"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/gitdiff"
git_service "code.gitea.io/gitea/services/repository"
)
@@ -275,7 +280,7 @@ func Diff(ctx *context.Context) {
)
if ctx.Data["PageIsWiki"] != nil {
- gitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
+ gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("Repo.GitRepo.GetCommit", err)
return
@@ -294,7 +299,7 @@ func Diff(ctx *context.Context) {
}
return
}
- if len(commitID) != git.SHAFullLength {
+ if len(commitID) != commit.ID.Type().FullLength() {
commitID = commit.ID.String()
}
@@ -370,9 +375,21 @@ func Diff(ctx *context.Context) {
note := &git.Note{}
err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
if err == nil {
- ctx.Data["Note"] = string(charset.ToUTF8WithFallback(note.Message))
ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
+ ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
+ }, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
+ if err != nil {
+ ctx.ServerError("RenderCommitMessage", err)
+ return
+ }
}
ctx.Data["BranchName"], err = commit.GetBranchName()
@@ -388,7 +405,7 @@ func Diff(ctx *context.Context) {
func RawDiff(ctx *context.Context) {
var gitRepo *git.Repository
if ctx.Data["PageIsWiki"] != nil {
- wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
+ wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("OpenRepository", err)
return
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index b69af3c61cc..b0570f97c3b 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -25,16 +25,18 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
csv_module "code.gitea.io/gitea/modules/csv"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/typesniffer"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/gitdiff"
)
@@ -125,7 +127,7 @@ func setCsvCompareContext(ctx *context.Context) {
return CsvDiffResult{nil, ""}
}
- errTooLarge := errors.New(ctx.Locale.Tr("repo.error.csv.too_large"))
+ errTooLarge := errors.New(ctx.Locale.TrString("repo.error.csv.too_large"))
csvReaderFromCommit := func(ctx *markup.RenderContext, blob *git.Blob) (*csv.Reader, io.Closer, error) {
if blob == nil {
@@ -142,7 +144,7 @@ func setCsvCompareContext(ctx *context.Context) {
return nil, nil, err
}
- csvReader, err := csv_module.CreateReaderAndDetermineDelimiter(ctx, charset.ToUTF8WithFallbackReader(reader))
+ csvReader, err := csv_module.CreateReaderAndDetermineDelimiter(ctx, charset.ToUTF8WithFallbackReader(reader, charset.ConvertOpts{}))
return csvReader, reader, err
}
@@ -310,13 +312,14 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch)
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch)
baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch)
+
if !baseIsCommit && !baseIsBranch && !baseIsTag {
// Check if baseBranch is short sha commit hash
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil {
ci.BaseBranch = baseCommit.ID.String()
ctx.Data["BaseBranch"] = ci.BaseBranch
baseIsCommit = true
- } else if ci.BaseBranch == git.EmptySHA {
+ } else if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() {
if isSameRepo {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch))
} else {
@@ -407,7 +410,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
ci.HeadRepo = ctx.Repo.Repository
ci.HeadGitRepo = ctx.Repo.GitRepo
} else if has {
- ci.HeadGitRepo, err = git.OpenRepository(ctx, ci.HeadRepo.RepoPath())
+ ci.HeadGitRepo, err = gitrepo.OpenRepository(ctx, ci.HeadRepo)
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
@@ -687,7 +690,7 @@ func PrepareCompareDiff(
}
func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repository) (branches, tags []string, err error) {
- gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
return nil, nil, err
}
@@ -698,7 +701,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
ListOptions: db.ListOptions{
ListAll: true,
},
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
})
if err != nil {
return nil, nil, err
@@ -755,7 +758,7 @@ func CompareDiff(ctx *context.Context) {
ListOptions: db.ListOptions{
ListAll: true,
},
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
})
if err != nil {
ctx.ServerError("GetBranches", err)
@@ -844,6 +847,7 @@ func CompareDiff(ctx *context.Context) {
}
}
+ ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanWrite(unit.TypeProjects)
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
@@ -874,7 +878,7 @@ func ExcerptBlob(ctx *context.Context) {
gitRepo := ctx.Repo.GitRepo
if ctx.FormBool("wiki") {
var err error
- gitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
+ gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("OpenRepository", err)
return
diff --git a/routers/web/repo/contributors.go b/routers/web/repo/contributors.go
new file mode 100644
index 00000000000..5fda17469e9
--- /dev/null
+++ b/routers/web/repo/contributors.go
@@ -0,0 +1,44 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "errors"
+ "net/http"
+
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/services/context"
+ contributors_service "code.gitea.io/gitea/services/repository"
+)
+
+const (
+ tplContributors base.TplName = "repo/activity"
+)
+
+// Contributors render the page to show repository contributors graph
+func Contributors(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.contributors")
+
+ ctx.Data["PageIsActivity"] = true
+ ctx.Data["PageIsContributors"] = true
+
+ ctx.PageData["contributionType"] = "commits"
+
+ ctx.PageData["repoLink"] = ctx.Repo.RepoLink
+
+ ctx.HTML(http.StatusOK, tplContributors)
+}
+
+// ContributorsData renders JSON of contributors along with their weekly commit statistics
+func ContributorsData(ctx *context.Context) {
+ if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
+ if errors.Is(err, contributors_service.ErrAwaitGeneration) {
+ ctx.Status(http.StatusAccepted)
+ return
+ }
+ ctx.ServerError("GetContributorStats", err)
+ } else {
+ ctx.JSON(http.StatusOK, contributorStats)
+ }
+}
diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go
index a9e2e2b2fad..c4a8baecca3 100644
--- a/routers/web/repo/download.go
+++ b/routers/web/repo/download.go
@@ -9,7 +9,6 @@ import (
"time"
git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/lfs"
@@ -17,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
)
// ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 5e7cd1caa39..8f3d9612eca 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -16,17 +16,17 @@ import (
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
files_service "code.gitea.io/gitea/services/repository/files"
)
@@ -161,13 +161,10 @@ func editFile(ctx *context.Context, isNewFile bool) {
}
d, _ := io.ReadAll(dataRc)
- if err := dataRc.Close(); err != nil {
- log.Error("Error whilst closing blob data: %v", err)
- }
buf = append(buf, d...)
- if content, err := charset.ToUTF8WithErr(buf); err != nil {
- log.Error("ToUTF8WithErr: %v", err)
+ if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
+ log.Error("ToUTF8: %v", err)
ctx.Data["FileContent"] = string(buf)
} else {
ctx.Data["FileContent"] = content
@@ -262,9 +259,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 {
if isNewFile {
- message = ctx.Tr("repo.editor.add", form.TreePath)
+ message = ctx.Locale.TrString("repo.editor.add", form.TreePath)
} else {
- message = ctx.Tr("repo.editor.update", form.TreePath)
+ message = ctx.Locale.TrString("repo.editor.update", form.TreePath)
}
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
@@ -415,7 +412,7 @@ func DiffPreviewPost(ctx *context.Context) {
}
if diff.NumFiles == 0 {
- ctx.PlainText(http.StatusOK, ctx.Tr("repo.editor.no_changes_to_show"))
+ ctx.PlainText(http.StatusOK, ctx.Locale.TrString("repo.editor.no_changes_to_show"))
return
}
ctx.Data["File"] = diff.Files[0]
@@ -482,7 +479,7 @@ func DeleteFilePost(ctx *context.Context) {
message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 {
- message = ctx.Tr("repo.editor.delete", ctx.Repo.TreePath)
+ message = ctx.Locale.TrString("repo.editor.delete", ctx.Repo.TreePath)
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
if len(form.CommitMessage) > 0 {
@@ -691,7 +688,7 @@ func UploadFilePost(ctx *context.Context) {
if dir == "" {
dir = "/"
}
- message = ctx.Tr("repo.editor.upload_files_to_dir", dir)
+ message = ctx.Locale.TrString("repo.editor.upload_files_to_dir", dir)
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
diff --git a/routers/web/repo/editor_test.go b/routers/web/repo/editor_test.go
index 67fb277d5c2..313fcfe33a2 100644
--- a/routers/web/repo/editor_test.go
+++ b/routers/web/repo/editor_test.go
@@ -7,8 +7,9 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
@@ -66,7 +67,7 @@ func TestGetClosestParentWithFiles(t *testing.T) {
repo := ctx.Repo.Repository
branch := repo.DefaultBranch
- gitRepo, _ := git.OpenRepository(git.DefaultContext, repo.RepoPath())
+ gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(branch)
var expectedTreePath string // Should return the root dir, empty string, since there are no subdirs in this repo
diff --git a/routers/web/repo/find.go b/routers/web/repo/find.go
index daefe59c8f2..07b37227988 100644
--- a/routers/web/repo/find.go
+++ b/routers/web/repo/find.go
@@ -7,7 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go
index 6ff385f9890..8fb6d930688 100644
--- a/routers/web/repo/githttp.go
+++ b/routers/web/repo/githttp.go
@@ -11,7 +11,7 @@ import (
"fmt"
"net/http"
"os"
- "path"
+ "path/filepath"
"regexp"
"strconv"
"strings"
@@ -24,13 +24,13 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/go-chi/cors"
@@ -90,11 +90,10 @@ func httpBase(ctx *context.Context) *serviceHandler {
isWiki := false
unitType := unit.TypeCode
- var wikiRepoName string
+
if strings.HasSuffix(reponame, ".wiki") {
isWiki = true
unitType = unit.TypeWiki
- wikiRepoName = reponame
reponame = reponame[:len(reponame)-5]
}
@@ -107,16 +106,16 @@ func httpBase(ctx *context.Context) *serviceHandler {
repoExist := true
repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, reponame)
if err != nil {
- if repo_model.IsErrRepoNotExist(err) {
- if redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, reponame); err == nil {
- context.RedirectToRepo(ctx.Base, redirectRepoID)
- return nil
- }
- repoExist = false
- } else {
+ if !repo_model.IsErrRepoNotExist(err) {
ctx.ServerError("GetRepositoryByName", err)
return nil
}
+
+ if redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, reponame); err == nil {
+ context.RedirectToRepo(ctx.Base, redirectRepoID)
+ return nil
+ }
+ repoExist = false
}
// Don't allow pushing if the repo is archived
@@ -184,7 +183,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
if repoExist {
// Because of special ref "refs/for" .. , need delay write permission check
- if git.SupportProcReceive {
+ if git.DefaultFeatures.SupportProcReceive {
accessMode = perm.AccessModeRead
}
@@ -292,22 +291,9 @@ func httpBase(ctx *context.Context) *serviceHandler {
environ = append(environ, repo_module.EnvRepoID+fmt.Sprintf("=%d", repo.ID))
- w := ctx.Resp
- r := ctx.Req
- cfg := &serviceConfig{
- UploadPack: true,
- ReceivePack: true,
- Env: environ,
- }
+ ctx.Req.URL.Path = strings.ToLower(ctx.Req.URL.Path) // blue: In case some repo name has upper case name
- r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
-
- dir := repo_model.RepoPath(username, reponame)
- if isWiki {
- dir = repo_model.RepoPath(username, wikiRepoName)
- }
-
- return &serviceHandler{cfg, w, r, dir, cfg.Env}
+ return &serviceHandler{repo, isWiki, environ}
}
var (
@@ -329,7 +315,7 @@ func dummyInfoRefs(ctx *context.Context) {
}
}()
- if err := git.InitRepository(ctx, tmpDir, true); err != nil {
+ if err := git.InitRepository(ctx, tmpDir, true, git.Sha1ObjectFormat.Name()); err != nil {
log.Error("Failed to init bare repo for git-receive-pack cache: %v", err)
return
}
@@ -352,32 +338,31 @@ func dummyInfoRefs(ctx *context.Context) {
_, _ = ctx.Write(infoRefsCache)
}
-type serviceConfig struct {
- UploadPack bool
- ReceivePack bool
- Env []string
-}
-
type serviceHandler struct {
- cfg *serviceConfig
- w http.ResponseWriter
- r *http.Request
- dir string
+ repo *repo_model.Repository
+ isWiki bool
environ []string
}
-func (h *serviceHandler) setHeaderNoCache() {
- h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
- h.w.Header().Set("Pragma", "no-cache")
- h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
+func (h *serviceHandler) getRepoDir() string {
+ if h.isWiki {
+ return h.repo.WikiPath()
+ }
+ return h.repo.RepoPath()
}
-func (h *serviceHandler) setHeaderCacheForever() {
+func setHeaderNoCache(ctx *context.Context) {
+ ctx.Resp.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
+ ctx.Resp.Header().Set("Pragma", "no-cache")
+ ctx.Resp.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
+}
+
+func setHeaderCacheForever(ctx *context.Context) {
now := time.Now().Unix()
expires := now + 31536000
- h.w.Header().Set("Date", fmt.Sprintf("%d", now))
- h.w.Header().Set("Expires", fmt.Sprintf("%d", expires))
- h.w.Header().Set("Cache-Control", "public, max-age=31536000")
+ ctx.Resp.Header().Set("Date", fmt.Sprintf("%d", now))
+ ctx.Resp.Header().Set("Expires", fmt.Sprintf("%d", expires))
+ ctx.Resp.Header().Set("Cache-Control", "public, max-age=31536000")
}
func containsParentDirectorySeparator(v string) bool {
@@ -394,71 +379,71 @@ func containsParentDirectorySeparator(v string) bool {
func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
-func (h *serviceHandler) sendFile(contentType, file string) {
+func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string) {
if containsParentDirectorySeparator(file) {
log.Error("request file path contains invalid path: %v", file)
- h.w.WriteHeader(http.StatusBadRequest)
+ ctx.Resp.WriteHeader(http.StatusBadRequest)
return
}
- reqFile := path.Join(h.dir, file)
+ reqFile := filepath.Join(h.getRepoDir(), file)
fi, err := os.Stat(reqFile)
if os.IsNotExist(err) {
- h.w.WriteHeader(http.StatusNotFound)
+ ctx.Resp.WriteHeader(http.StatusNotFound)
return
}
- h.w.Header().Set("Content-Type", contentType)
- h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
- h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
- http.ServeFile(h.w, h.r, reqFile)
+ ctx.Resp.Header().Set("Content-Type", contentType)
+ ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
+ ctx.Resp.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
+ http.ServeFile(ctx.Resp, ctx.Req, reqFile)
}
// one or more key=value pairs separated by colons
var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`)
-func prepareGitCmdWithAllowedService(service string, h *serviceHandler) (*git.Command, error) {
- if service == "receive-pack" && h.cfg.ReceivePack {
- return git.NewCommand(h.r.Context(), "receive-pack"), nil
+func prepareGitCmdWithAllowedService(ctx *context.Context, service string) (*git.Command, error) {
+ if service == "receive-pack" {
+ return git.NewCommand(ctx, "receive-pack"), nil
}
- if service == "upload-pack" && h.cfg.UploadPack {
- return git.NewCommand(h.r.Context(), "upload-pack"), nil
+ if service == "upload-pack" {
+ return git.NewCommand(ctx, "upload-pack"), nil
}
return nil, fmt.Errorf("service %q is not allowed", service)
}
-func serviceRPC(h *serviceHandler, service string) {
+func serviceRPC(ctx *context.Context, h *serviceHandler, service string) {
defer func() {
- if err := h.r.Body.Close(); err != nil {
+ if err := ctx.Req.Body.Close(); err != nil {
log.Error("serviceRPC: Close: %v", err)
}
}()
expectedContentType := fmt.Sprintf("application/x-git-%s-request", service)
- if h.r.Header.Get("Content-Type") != expectedContentType {
- log.Error("Content-Type (%q) doesn't match expected: %q", h.r.Header.Get("Content-Type"), expectedContentType)
- h.w.WriteHeader(http.StatusUnauthorized)
+ if ctx.Req.Header.Get("Content-Type") != expectedContentType {
+ log.Error("Content-Type (%q) doesn't match expected: %q", ctx.Req.Header.Get("Content-Type"), expectedContentType)
+ ctx.Resp.WriteHeader(http.StatusUnauthorized)
return
}
- cmd, err := prepareGitCmdWithAllowedService(service, h)
+ cmd, err := prepareGitCmdWithAllowedService(ctx, service)
if err != nil {
log.Error("Failed to prepareGitCmdWithService: %v", err)
- h.w.WriteHeader(http.StatusUnauthorized)
+ ctx.Resp.WriteHeader(http.StatusUnauthorized)
return
}
- h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
+ ctx.Resp.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
- reqBody := h.r.Body
+ reqBody := ctx.Req.Body
// Handle GZIP.
- if h.r.Header.Get("Content-Encoding") == "gzip" {
+ if ctx.Req.Header.Get("Content-Encoding") == "gzip" {
reqBody, err = gzip.NewReader(reqBody)
if err != nil {
log.Error("Fail to create gzip reader: %v", err)
- h.w.WriteHeader(http.StatusInternalServerError)
+ ctx.Resp.WriteHeader(http.StatusInternalServerError)
return
}
}
@@ -466,23 +451,23 @@ func serviceRPC(h *serviceHandler, service string) {
// set this for allow pre-receive and post-receive execute
h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service)
- if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
+ if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
}
var stderr bytes.Buffer
- cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.dir)
- cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir))
+ cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.getRepoDir())
+ cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.getRepoDir()))
if err := cmd.Run(&git.RunOpts{
- Dir: h.dir,
+ Dir: h.getRepoDir(),
Env: append(os.Environ(), h.environ...),
- Stdout: h.w,
+ Stdout: ctx.Resp,
Stdin: reqBody,
Stderr: &stderr,
UseContextTimeout: true,
}); err != nil {
if err.Error() != "signal: killed" {
- log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
+ log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.getRepoDir(), err, stderr.String())
}
return
}
@@ -492,7 +477,7 @@ func serviceRPC(h *serviceHandler, service string) {
func ServiceUploadPack(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
- serviceRPC(h, "upload-pack")
+ serviceRPC(ctx, h, "upload-pack")
}
}
@@ -500,12 +485,12 @@ func ServiceUploadPack(ctx *context.Context) {
func ServiceReceivePack(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
- serviceRPC(h, "receive-pack")
+ serviceRPC(ctx, h, "receive-pack")
}
}
-func getServiceType(r *http.Request) string {
- serviceType := r.FormValue("service")
+func getServiceType(ctx *context.Context) string {
+ serviceType := ctx.Req.FormValue("service")
if !strings.HasPrefix(serviceType, "git-") {
return ""
}
@@ -534,28 +519,28 @@ func GetInfoRefs(ctx *context.Context) {
if h == nil {
return
}
- h.setHeaderNoCache()
- service := getServiceType(h.r)
- cmd, err := prepareGitCmdWithAllowedService(service, h)
+ setHeaderNoCache(ctx)
+ service := getServiceType(ctx)
+ cmd, err := prepareGitCmdWithAllowedService(ctx, service)
if err == nil {
- if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
+ if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
}
h.environ = append(os.Environ(), h.environ...)
- refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir})
+ refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.getRepoDir()})
if err != nil {
log.Error(fmt.Sprintf("%v - %s", err, string(refs)))
}
- h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
- h.w.WriteHeader(http.StatusOK)
- _, _ = h.w.Write(packetWrite("# service=git-" + service + "\n"))
- _, _ = h.w.Write([]byte("0000"))
- _, _ = h.w.Write(refs)
+ ctx.Resp.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
+ ctx.Resp.WriteHeader(http.StatusOK)
+ _, _ = ctx.Resp.Write(packetWrite("# service=git-" + service + "\n"))
+ _, _ = ctx.Resp.Write([]byte("0000"))
+ _, _ = ctx.Resp.Write(refs)
} else {
- updateServerInfo(ctx, h.dir)
- h.sendFile("text/plain; charset=utf-8", "info/refs")
+ updateServerInfo(ctx, h.getRepoDir())
+ h.sendFile(ctx, "text/plain; charset=utf-8", "info/refs")
}
}
@@ -564,12 +549,12 @@ func GetTextFile(p string) func(*context.Context) {
return func(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
- h.setHeaderNoCache()
+ setHeaderNoCache(ctx)
file := ctx.Params("file")
if file != "" {
- h.sendFile("text/plain", "objects/info/"+file)
+ h.sendFile(ctx, "text/plain", "objects/info/"+file)
} else {
- h.sendFile("text/plain", p)
+ h.sendFile(ctx, "text/plain", p)
}
}
}
@@ -579,8 +564,8 @@ func GetTextFile(p string) func(*context.Context) {
func GetInfoPacks(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
- h.setHeaderCacheForever()
- h.sendFile("text/plain; charset=utf-8", "objects/info/packs")
+ setHeaderCacheForever(ctx)
+ h.sendFile(ctx, "text/plain; charset=utf-8", "objects/info/packs")
}
}
@@ -588,8 +573,8 @@ func GetInfoPacks(ctx *context.Context) {
func GetLooseObject(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
- h.setHeaderCacheForever()
- h.sendFile("application/x-git-loose-object", fmt.Sprintf("objects/%s/%s",
+ setHeaderCacheForever(ctx)
+ h.sendFile(ctx, "application/x-git-loose-object", fmt.Sprintf("objects/%s/%s",
ctx.Params("head"), ctx.Params("hash")))
}
}
@@ -598,8 +583,8 @@ func GetLooseObject(ctx *context.Context) {
func GetPackFile(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
- h.setHeaderCacheForever()
- h.sendFile("application/x-git-packed-objects", "objects/pack/pack-"+ctx.Params("file")+".pack")
+ setHeaderCacheForever(ctx)
+ h.sendFile(ctx, "application/x-git-packed-objects", "objects/pack/pack-"+ctx.Params("file")+".pack")
}
}
@@ -607,7 +592,7 @@ func GetPackFile(ctx *context.Context) {
func GetIdxFile(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
- h.setHeaderCacheForever()
- h.sendFile("application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
+ setHeaderCacheForever(ctx)
+ h.sendFile(ctx, "application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
}
}
diff --git a/routers/web/repo/helper.go b/routers/web/repo/helper.go
index a98abe566f8..5e1e116018e 100644
--- a/routers/web/repo/helper.go
+++ b/routers/web/repo/helper.go
@@ -8,8 +8,8 @@ import (
"sort"
"code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/services/context"
)
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index ba8b49774b9..65e74a2d90e 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -31,7 +31,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
issue_template "code.gitea.io/gitea/modules/issue/template"
@@ -43,11 +43,12 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates/vars"
"code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
issue_service "code.gitea.io/gitea/services/issue"
@@ -237,10 +238,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
}
- isShowClosed := ctx.FormString("state") == "closed"
- // if open issues are zero and close don't, use closed as default
+ var isShowClosed util.OptionalBool
+ switch ctx.FormString("state") {
+ case "closed":
+ isShowClosed = util.OptionalBoolTrue
+ case "all":
+ isShowClosed = util.OptionalBoolNone
+ default:
+ isShowClosed = util.OptionalBoolFalse
+ }
+ // if there are closed issues and no open issues, default to showing all issues
if len(ctx.FormString("state")) == 0 && issueStats.OpenCount == 0 && issueStats.ClosedCount != 0 {
- isShowClosed = true
+ isShowClosed = util.OptionalBoolNone
}
if repo.IsTimetrackerEnabled(ctx) {
@@ -260,10 +269,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
var total int
- if !isShowClosed {
- total = int(issueStats.OpenCount)
- } else {
+ switch isShowClosed {
+ case util.OptionalBoolTrue:
total = int(issueStats.ClosedCount)
+ case util.OptionalBoolNone:
+ total = int(issueStats.OpenCount + issueStats.ClosedCount)
+ default:
+ total = int(issueStats.OpenCount)
}
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
@@ -282,7 +294,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ReviewedID: reviewedID,
MilestoneIDs: mileIDs,
ProjectID: projectID,
- IsClosed: util.OptionalBoolOf(isShowClosed),
+ IsClosed: isShowClosed,
IsPull: isPullOption,
LabelIDs: labelIDs,
SortType: sortType,
@@ -428,6 +440,9 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["OpenCount"] = issueStats.OpenCount
ctx.Data["ClosedCount"] = issueStats.ClosedCount
linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t"
+ ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Link,
+ url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels),
+ mentionedID, projectID, assigneeID, posterID, archived)
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived)
@@ -442,11 +457,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["ProjectID"] = projectID
ctx.Data["AssigneeID"] = assigneeID
ctx.Data["PosterID"] = posterID
- ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["Keyword"] = keyword
- if isShowClosed {
+ switch isShowClosed {
+ case util.OptionalBoolTrue:
ctx.Data["State"] = "closed"
- } else {
+ case util.OptionalBoolNone:
+ ctx.Data["State"] = "all"
+ default:
ctx.Data["State"] = "open"
}
ctx.Data["ShowArchivedLabels"] = archived
@@ -510,9 +527,8 @@ func Issues(ctx *context.Context) {
func renderMilestones(ctx *context.Context) {
// Get milestones
- milestones, _, err := issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
+ milestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: ctx.Repo.Repository.ID,
- State: api.StateAll,
})
if err != nil {
ctx.ServerError("GetAllRepoMilestones", err)
@@ -534,17 +550,17 @@ func renderMilestones(ctx *context.Context) {
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.Repository) {
var err error
- ctx.Data["OpenMilestones"], _, err = issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
- RepoID: repo.ID,
- State: api.StateOpen,
+ ctx.Data["OpenMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
+ RepoID: repo.ID,
+ IsClosed: util.OptionalBoolFalse,
})
if err != nil {
ctx.ServerError("GetMilestones", err)
return
}
- ctx.Data["ClosedMilestones"], _, err = issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
- RepoID: repo.ID,
- State: api.StateClosed,
+ ctx.Data["ClosedMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
+ RepoID: repo.ID,
+ IsClosed: util.OptionalBoolTrue,
})
if err != nil {
ctx.ServerError("GetMilestones", err)
@@ -696,16 +712,12 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
tmp.ItemID = -review.ReviewerTeamID
}
- if ctx.Repo.IsAdmin() {
- // Admin can dismiss or re-request any review requests
+ if canChooseReviewer {
+ // Users who can choose reviewers can also remove review requests
tmp.CanChange = true
} else if ctx.Doer != nil && ctx.Doer.ID == review.ReviewerID && review.Type == issues_model.ReviewTypeRequest {
// A user can refuse review requests
tmp.CanChange = true
- } else if (canChooseReviewer || (ctx.Doer != nil && ctx.Doer.ID == issue.PosterID)) && review.Type != issues_model.ReviewTypeRequest &&
- ctx.Doer.ID != review.ReviewerID {
- // The poster of the PR, a manager, or official reviewers can re-request review from other reviewers
- tmp.CanChange = true
}
pullReviews = append(pullReviews, tmp)
@@ -978,17 +990,17 @@ func NewIssue(ctx *context.Context) {
}
ctx.Data["Tags"] = tags
- _, templateErrs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
+ ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
for k, v := range errs {
- templateErrs[k] = v
+ ret.TemplateErrors[k] = v
}
if ctx.Written() {
return
}
- if len(templateErrs) > 0 {
- ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true)
+ if len(ret.TemplateErrors) > 0 {
+ ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true)
}
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypeIssues)
@@ -1021,7 +1033,7 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string
})
if err != nil {
log.Debug("render flash error: %v", err)
- flashError = ctx.Tr("repo.issues.choose.ignore_invalid_templates")
+ flashError = ctx.Locale.TrString("repo.issues.choose.ignore_invalid_templates")
}
return flashError
}
@@ -1031,11 +1043,11 @@ func NewIssueChooseTemplate(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.issues.new")
ctx.Data["PageIsIssueList"] = true
- issueTemplates, errs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
- ctx.Data["IssueTemplates"] = issueTemplates
+ ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
+ ctx.Data["IssueTemplates"] = ret.IssueTemplates
- if len(errs) > 0 {
- ctx.Flash.Warning(renderErrorOfTemplates(ctx, errs), true)
+ if len(ret.TemplateErrors) > 0 {
+ ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true)
}
if !issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo) {
@@ -1424,7 +1436,7 @@ func ViewIssue(ctx *context.Context) {
return
}
- ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
+ ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, emoji.ReplaceAliases(issue.Title))
iw := new(issues_model.IssueWatch)
if ctx.Doer != nil {
@@ -1437,12 +1449,13 @@ func ViewIssue(ctx *context.Context) {
}
}
ctx.Data["IssueWatch"] = iw
-
issue.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1509,18 +1522,9 @@ func ViewIssue(ctx *context.Context) {
}
if issue.IsPull {
- canChooseReviewer := ctx.Repo.CanWrite(unit.TypePullRequests)
+ canChooseReviewer := false
if ctx.Doer != nil && ctx.IsSigned {
- if !canChooseReviewer {
- canChooseReviewer = ctx.Doer.ID == issue.PosterID
- }
- if !canChooseReviewer {
- canChooseReviewer, err = issues_model.IsOfficialReviewer(ctx, issue, ctx.Doer)
- if err != nil {
- ctx.ServerError("IsOfficialReviewer", err)
- return
- }
- }
+ canChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, issue)
}
RetrieveRepoReviewers(ctx, repo, issue, canChooseReviewer)
@@ -1602,10 +1606,12 @@ func ViewIssue(ctx *context.Context) {
}
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1637,7 +1643,7 @@ func ViewIssue(ctx *context.Context) {
}
ghostMilestone := &issues_model.Milestone{
ID: -1,
- Name: ctx.Tr("repo.issues.deleted_milestone"),
+ Name: ctx.Locale.TrString("repo.issues.deleted_milestone"),
}
if comment.OldMilestoneID > 0 && comment.OldMilestone == nil {
comment.OldMilestone = ghostMilestone
@@ -1654,7 +1660,7 @@ func ViewIssue(ctx *context.Context) {
ghostProject := &project_model.Project{
ID: -1,
- Title: ctx.Tr("repo.issues.deleted_project"),
+ Title: ctx.Locale.TrString("repo.issues.deleted_project"),
}
if comment.OldProjectID > 0 && comment.OldProject == nil {
@@ -1679,10 +1685,12 @@ func ViewIssue(ctx *context.Context) {
}
} else if comment.Type.HasContentSupport() {
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1710,6 +1718,10 @@ func ViewIssue(ctx *context.Context) {
for _, codeComments := range comment.Review.CodeComments {
for _, lineComments := range codeComments {
for _, c := range lineComments {
+ if err := c.LoadAttachments(ctx); err != nil {
+ ctx.ServerError("LoadAttachments", err)
+ return
+ }
// Check tag.
role, ok = marked[c.PosterID]
if ok {
@@ -1842,6 +1854,8 @@ func ViewIssue(ctx *context.Context) {
mergeStyle = repo_model.MergeStyleRebaseMerge
} else if prConfig.AllowSquash {
mergeStyle = repo_model.MergeStyleSquash
+ } else if prConfig.AllowFastForwardOnly {
+ mergeStyle = repo_model.MergeStyleFastForwardOnly
} else if prConfig.AllowManualMerge {
mergeStyle = repo_model.MergeStyleManuallyMerged
}
@@ -1963,7 +1977,7 @@ func ViewIssue(ctx *context.Context) {
return
}
- ctx.Data["BlockingDependencies"], ctx.Data["BlockingByDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
+ ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
if ctx.Written() {
return
}
@@ -2024,38 +2038,34 @@ func ViewIssue(ctx *context.Context) {
// checkBlockedByIssues return canRead and notPermitted
func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) {
- var (
- lastRepoID int64
- lastPerm access_model.Permission
- )
- for i, blocker := range blockers {
+ repoPerms := make(map[int64]access_model.Permission)
+ repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
+ for _, blocker := range blockers {
// Get the permissions for this repository
- perm := lastPerm
- if lastRepoID != blocker.Repository.ID {
- if blocker.Repository.ID == ctx.Repo.Repository.ID {
- perm = ctx.Repo.Permission
- } else {
- var err error
- perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return nil, nil
- }
+ // If the repo ID exists in the map, return the exist permissions
+ // else get the permission and add it to the map
+ var perm access_model.Permission
+ existPerm, ok := repoPerms[blocker.RepoID]
+ if ok {
+ perm = existPerm
+ } else {
+ var err error
+ perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return nil, nil
}
- lastRepoID = blocker.Repository.ID
+ repoPerms[blocker.RepoID] = perm
}
-
- // check permission
- if !perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
- blockers[len(notPermitted)], blockers[i] = blocker, blockers[len(notPermitted)]
- notPermitted = blockers[:len(notPermitted)+1]
+ if perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
+ canRead = append(canRead, blocker)
+ } else {
+ notPermitted = append(notPermitted, blocker)
}
}
- blockers = blockers[len(notPermitted):]
- sortDependencyInfo(blockers)
+ sortDependencyInfo(canRead)
sortDependencyInfo(notPermitted)
-
- return blockers, notPermitted
+ return canRead, notPermitted
}
func sortDependencyInfo(blockers []*issues_model.DependencyInfo) {
@@ -2239,10 +2249,12 @@ func UpdateIssueContent(ctx *context.Context) {
}
content, err := markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -3148,10 +3160,12 @@ func UpdateCommentContent(ctx *context.Context) {
}
content, err := markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go
index 473ab260f3e..fce0eccc7ba 100644
--- a/routers/web/repo/issue_content_history.go
+++ b/routers/web/repo/issue_content_history.go
@@ -11,11 +11,11 @@ import (
"code.gitea.io/gitea/models/avatars"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/services/context"
"github.com/sergi/go-diff/diffmatchpatch"
)
@@ -56,12 +56,12 @@ func GetContentHistoryList(ctx *context.Context) {
for _, item := range items {
var actionText string
if item.IsDeleted {
- actionTextDeleted := ctx.Locale.Tr("repo.issues.content_history.deleted")
+ actionTextDeleted := ctx.Locale.TrString("repo.issues.content_history.deleted")
actionText = "" + actionTextDeleted + ""
} else if item.IsFirstCreated {
- actionText = ctx.Locale.Tr("repo.issues.content_history.created")
+ actionText = ctx.Locale.TrString("repo.issues.content_history.created")
} else {
- actionText = ctx.Locale.Tr("repo.issues.content_history.edited")
+ actionText = ctx.Locale.TrString("repo.issues.content_history.edited")
}
username := item.UserName
@@ -193,15 +193,29 @@ func SoftDeleteContentHistory(ctx *context.Context) {
var comment *issues_model.Comment
var history *issues_model.ContentHistory
var err error
+
+ if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
+ log.Error("can not get issue content history %v. err=%v", historyID, err)
+ return
+ }
+ if history.IssueID != issue.ID {
+ ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
+ return
+ }
if commentID != 0 {
+ if history.CommentID != commentID {
+ ctx.NotFound("CompareCommentID", issues_model.ErrCommentNotExist{})
+ return
+ }
+
if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil {
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
return
}
- }
- if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
- log.Error("can not get issue content history %v. err=%v", historyID, err)
- return
+ if comment.IssueID != issue.ID {
+ ctx.NotFound("CompareIssueID", issues_model.ErrCommentNotExist{})
+ return
+ }
}
canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history)
diff --git a/routers/web/repo/issue_dependency.go b/routers/web/repo/issue_dependency.go
index 022ec3ae3e2..e3b85ee638e 100644
--- a/routers/web/repo/issue_dependency.go
+++ b/routers/web/repo/issue_dependency.go
@@ -8,8 +8,8 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
)
// AddDependency adds new dependencies
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index dd3e2803b4e..9dedaefa4be 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -10,12 +10,12 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
issue_service "code.gitea.io/gitea/services/issue"
)
diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go
index e0d49e44e18..93fc72300b3 100644
--- a/routers/web/repo/issue_label_test.go
+++ b/routers/web/repo/issue_label_test.go
@@ -10,10 +10,10 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
"github.com/stretchr/testify/assert"
@@ -123,7 +123,7 @@ func TestDeleteLabel(t *testing.T) {
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2})
unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{LabelID: 2})
- assert.Equal(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg)
+ assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg)
}
func TestUpdateIssueLabel_Clear(t *testing.T) {
diff --git a/routers/web/repo/issue_lock.go b/routers/web/repo/issue_lock.go
index f83109d9b3f..1d5fc8a5f39 100644
--- a/routers/web/repo/issue_lock.go
+++ b/routers/web/repo/issue_lock.go
@@ -5,8 +5,8 @@ package repo
import (
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/repo/issue_pin.go b/routers/web/repo/issue_pin.go
index f853f72335e..365c812681a 100644
--- a/routers/web/repo/issue_pin.go
+++ b/routers/web/repo/issue_pin.go
@@ -7,9 +7,9 @@ import (
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
)
// IssuePinOrUnpin pin or unpin a Issue
@@ -90,6 +90,12 @@ func IssuePinMove(ctx *context.Context) {
return
}
+ if issue.RepoID != ctx.Repo.Repository.ID {
+ ctx.Status(http.StatusNotFound)
+ log.Error("Issue does not belong to this repository")
+ return
+ }
+
err = issue.MovePin(ctx, form.Position)
if err != nil {
ctx.Status(http.StatusInternalServerError)
diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go
index ab9fe3e69d9..70d42b27c0a 100644
--- a/routers/web/repo/issue_stopwatch.go
+++ b/routers/web/repo/issue_stopwatch.go
@@ -9,8 +9,8 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
+ "code.gitea.io/gitea/services/context"
)
// IssueStopwatch creates or stops a stopwatch for the given issue.
diff --git a/routers/web/repo/issue_timetrack.go b/routers/web/repo/issue_timetrack.go
index c9bf861b844..241e4340499 100644
--- a/routers/web/repo/issue_timetrack.go
+++ b/routers/web/repo/issue_timetrack.go
@@ -9,9 +9,9 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index 1cb5cc7162d..8b033f3b17a 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -8,8 +8,13 @@ import (
"strconv"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
+)
+
+const (
+ tplWatching base.TplName = "repo/issue/view_content/watching"
)
// IssueWatch sets issue watching
@@ -52,5 +57,7 @@ func IssueWatch(ctx *context.Context) {
return
}
- ctx.Redirect(issue.Link())
+ ctx.Data["Issue"] = issue
+ ctx.Data["IssueWatch"] = &issues_model.IssueWatch{IsWatching: watch}
+ ctx.HTML(http.StatusOK, tplWatching)
}
diff --git a/routers/web/repo/middlewares.go b/routers/web/repo/middlewares.go
index 5f4a219aa33..420931c5fb7 100644
--- a/routers/web/repo/middlewares.go
+++ b/routers/web/repo/middlewares.go
@@ -9,8 +9,10 @@ import (
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/services/context"
+ user_service "code.gitea.io/gitea/services/user"
)
// SetEditorconfigIfExists set editor config as render variable
@@ -55,8 +57,12 @@ func SetDiffViewStyle(ctx *context.Context) {
}
ctx.Data["IsSplitStyle"] = style == "split"
- if err := user_model.UpdateUserDiffViewStyle(ctx, ctx.Doer, style); err != nil {
- ctx.ServerError("ErrUpdateDiffViewStyle", err)
+
+ opts := &user_service.UpdateOptions{
+ DiffViewStyle: optional.Some(style),
+ }
+ if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
+ ctx.ServerError("UpdateUser", err)
}
}
@@ -92,23 +98,15 @@ func SetWhitespaceBehavior(ctx *context.Context) {
// SetShowOutdatedComments set the show outdated comments option as context variable
func SetShowOutdatedComments(ctx *context.Context) {
showOutdatedCommentsValue := ctx.FormString("show-outdated")
- // var showOutdatedCommentsValue string
-
if showOutdatedCommentsValue != "true" && showOutdatedCommentsValue != "false" {
// invalid or no value for this form string -> use default or stored user setting
+ showOutdatedCommentsValue = "true"
if ctx.IsSigned {
- showOutdatedCommentsValue, _ = user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, "false")
- } else {
- // not logged in user -> use the default value
- showOutdatedCommentsValue = "false"
+ showOutdatedCommentsValue, _ = user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, showOutdatedCommentsValue)
}
- } else {
+ } else if ctx.IsSigned {
// valid value -> update user setting if user is logged in
- if ctx.IsSigned {
- _ = user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, showOutdatedCommentsValue)
- }
+ _ = user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, showOutdatedCommentsValue)
}
-
- showOutdatedComments, _ := strconv.ParseBool(showOutdatedCommentsValue)
- ctx.Data["ShowOutdatedComments"] = showOutdatedComments
+ ctx.Data["ShowOutdatedComments"], _ = strconv.ParseBool(showOutdatedCommentsValue)
}
diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go
index b70901d5f28..97b0c425ea3 100644
--- a/routers/web/repo/migrate.go
+++ b/routers/web/repo/migrate.go
@@ -15,13 +15,13 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
"code.gitea.io/gitea/services/task"
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index 0e6f6307474..49ac94aaf15 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -12,14 +12,13 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/issue"
@@ -46,18 +45,13 @@ func Milestones(ctx *context.Context) {
page = 1
}
- state := structs.StateOpen
- if isShowClosed {
- state = structs.StateClosed
- }
-
- miles, total, err := issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
+ miles, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoID: ctx.Repo.Repository.ID,
- State: state,
+ IsClosed: util.OptionalBoolOf(isShowClosed),
SortType: sortType,
Name: keyword,
})
@@ -80,17 +74,19 @@ func Milestones(ctx *context.Context) {
url.QueryEscape(keyword), url.QueryEscape(sortType))
if ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
- if err := miles.LoadTotalTrackedTimes(ctx); err != nil {
+ if err := issues_model.MilestoneList(miles).LoadTotalTrackedTimes(ctx); err != nil {
ctx.ServerError("LoadTotalTrackedTimes", err)
return
}
}
for _, m := range miles {
m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, m.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -281,10 +277,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
}
milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, milestone.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -296,8 +294,8 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
issues(ctx, milestoneID, projectID, util.OptionalBoolNone)
- ret, _ := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
- ctx.Data["NewIssueChooseTemplate"] = len(ret) > 0
+ ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
+ ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false)
ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true)
diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go
index ac9e64d774e..6ed5909dcf6 100644
--- a/routers/web/repo/packages.go
+++ b/routers/web/repo/packages.go
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
)
const (
diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go
index c04435cf1b4..0dee02dd9ca 100644
--- a/routers/web/repo/patch.go
+++ b/routers/web/repo/patch.go
@@ -10,10 +10,10 @@ import (
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/repository/files"
)
@@ -79,7 +79,7 @@ func NewDiffPatchPost(ctx *context.Context) {
// `message` will be both the summary and message combined
message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 {
- message = ctx.Tr("repo.editor.patch")
+ message = ctx.Locale.TrString("repo.editor.patch")
}
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 5694575b468..1f9ee727c36 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -17,13 +17,13 @@ import (
attachment_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
@@ -90,10 +90,12 @@ func Projects(ctx *context.Context) {
for i := range projects {
projects[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, projects[i].Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -313,7 +315,7 @@ func ViewProject(ctx *context.Context) {
}
if boards[0].ID == 0 {
- boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
+ boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
@@ -337,16 +339,16 @@ func ViewProject(ctx *context.Context) {
linkedPrsMap := make(map[int64][]*issues_model.Issue)
for _, issuesList := range issuesMap {
for _, issue := range issuesList {
- var referencedIds []int64
+ var referencedIDs []int64
for _, comment := range issue.Comments {
if comment.RefIssueID != 0 && comment.RefIsPull {
- referencedIds = append(referencedIds, comment.RefIssueID)
+ referencedIDs = append(referencedIDs, comment.RefIssueID)
}
}
- if len(referencedIds) > 0 {
+ if len(referencedIDs) > 0 {
if linkedPrs, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
- IssueIDs: referencedIds,
+ IssueIDs: referencedIDs,
IsPull: util.OptionalBoolTrue,
}); err == nil {
linkedPrsMap[issue.ID] = linkedPrs
@@ -357,10 +359,12 @@ func ViewProject(ctx *context.Context) {
ctx.Data["LinkedPRs"] = linkedPrsMap
project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, project.Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -629,7 +633,7 @@ func MoveIssues(ctx *context.Context) {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
- Title: ctx.Tr("repo.projects.type.uncategorized"),
+ Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
diff --git a/routers/web/repo/projects_test.go b/routers/web/repo/projects_test.go
index 6698d47028b..479f8c55a22 100644
--- a/routers/web/repo/projects_test.go
+++ b/routers/web/repo/projects_test.go
@@ -7,7 +7,7 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/contexttest"
+ "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index ec109ed665c..b1521a2112a 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -27,18 +27,21 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
issue_template "code.gitea.io/gitea/modules/issue/template"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/automerge"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/gitdiff"
notify_service "code.gitea.io/gitea/services/notify"
@@ -185,7 +188,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
ListOptions: db.ListOptions{
ListAll: true,
},
- IsDeletedBranch: util.OptionalBoolFalse,
+ IsDeletedBranch: optional.Some(false),
// Add it as the first option
ExcludeBranchNames: []string{ctx.Repo.Repository.DefaultBranch},
})
@@ -333,7 +336,7 @@ func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) {
ctx.ServerError("LoadRepo", err)
return nil, false
}
- ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
+ ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, emoji.ReplaceAliases(issue.Title))
ctx.Data["Issue"] = issue
if !issue.IsPull {
@@ -530,7 +533,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
if pull.BaseRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
baseGitRepo = ctx.Repo.GitRepo
} else {
- baseGitRepo, err := git.OpenRepository(ctx, pull.BaseRepo.RepoPath())
+ baseGitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo)
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
@@ -582,7 +585,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
var headBranchSha string
// HeadRepo may be missing
if pull.HeadRepo != nil {
- headGitRepo, err := git.OpenRepository(ctx, pull.HeadRepo.RepoPath())
+ headGitRepo, err := gitrepo.OpenRepository(ctx, pull.HeadRepo)
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
@@ -651,9 +654,35 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
}
if pb != nil && pb.EnableStatusCheck {
+
+ var missingRequiredChecks []string
+ for _, requiredContext := range pb.StatusCheckContexts {
+ contextFound := false
+ matchesRequiredContext := createRequiredContextMatcher(requiredContext)
+ for _, presentStatus := range commitStatuses {
+ if matchesRequiredContext(presentStatus.Context) {
+ contextFound = true
+ break
+ }
+ }
+
+ if !contextFound {
+ missingRequiredChecks = append(missingRequiredChecks, requiredContext)
+ }
+ }
+ ctx.Data["MissingRequiredChecks"] = missingRequiredChecks
+
ctx.Data["is_context_required"] = func(context string) bool {
for _, c := range pb.StatusCheckContexts {
- if gp, err := glob.Compile(c); err == nil && gp.Match(context) {
+ if c == context {
+ return true
+ }
+ if gp, err := glob.Compile(c); err != nil {
+ // All newly created status_check_contexts are checked to ensure they are valid glob expressions before being stored in the database.
+ // But some old status_check_context created before glob was introduced may be invalid glob expressions.
+ // So log the error here for debugging.
+ log.Error("compile glob %q: %v", c, err)
+ } else if gp.Match(context) {
return true
}
}
@@ -711,10 +740,22 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return compareInfo
}
+func createRequiredContextMatcher(requiredContext string) func(string) bool {
+ if gp, err := glob.Compile(requiredContext); err == nil {
+ return func(contextToCheck string) bool {
+ return gp.Match(contextToCheck)
+ }
+ }
+
+ return func(contextToCheck string) bool {
+ return requiredContext == contextToCheck
+ }
+}
+
type pullCommitList struct {
Commits []pull_service.CommitInfo `json:"commits"`
LastReviewCommitSha string `json:"last_review_commit_sha"`
- Locale map[string]string `json:"locale"`
+ Locale map[string]any `json:"locale"`
}
// GetPullCommits get all commits for given pull request
@@ -732,7 +773,7 @@ func GetPullCommits(ctx *context.Context) {
}
// Get the needed locale
- resp.Locale = map[string]string{
+ resp.Locale = map[string]any{
"lang": ctx.Locale.Language(),
"show_all_commits": ctx.Tr("repo.pulls.show_all_commits"),
"stats_num_commits": ctx.TrN(len(commits), "repo.activity.git_stats_commit_1", "repo.activity.git_stats_commit_n", len(commits)),
@@ -929,6 +970,19 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
return
}
+ for _, file := range diff.Files {
+ for _, section := range file.Sections {
+ for _, line := range section.Lines {
+ for _, comment := range line.Comments {
+ if err := comment.LoadAttachments(ctx); err != nil {
+ ctx.ServerError("LoadAttachments", err)
+ return
+ }
+ }
+ }
+ }
+ }
+
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
if err != nil {
ctx.ServerError("LoadProtectedBranch", err)
@@ -1141,30 +1195,28 @@ func MergePullRequest(ctx *context.Context) {
switch {
case errors.Is(err, pull_service.ErrIsClosed):
if issue.IsPull {
- ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
+ ctx.JSONError(ctx.Tr("repo.pulls.is_closed"))
} else {
- ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
+ ctx.JSONError(ctx.Tr("repo.issues.closed_title"))
}
case errors.Is(err, pull_service.ErrUserNotAllowedToMerge):
- ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
+ ctx.JSONError(ctx.Tr("repo.pulls.update_not_allowed"))
case errors.Is(err, pull_service.ErrHasMerged):
- ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
+ ctx.JSONError(ctx.Tr("repo.pulls.has_merged"))
case errors.Is(err, pull_service.ErrIsWorkInProgress):
- ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
+ ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip"))
case errors.Is(err, pull_service.ErrNotMergableState):
- ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
+ ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case models.IsErrDisallowedToMerge(err):
- ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
+ ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case asymkey_service.IsErrWontSign(err):
- ctx.Flash.Error(err.Error()) // has no translation ...
+ ctx.JSONError(err.Error()) // has no translation ...
case errors.Is(err, pull_service.ErrDependenciesLeft):
- ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
+ ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
default:
ctx.ServerError("WebCheck", err)
- return
}
- ctx.Redirect(issue.Link())
return
}
@@ -1172,18 +1224,18 @@ func MergePullRequest(ctx *context.Context) {
if manuallyMerged {
if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
switch {
-
case models.IsErrInvalidMergeStyle(err):
- ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
+ ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
case strings.Contains(err.Error(), "Wrong commit ID"):
- ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
+ ctx.JSONError(ctx.Tr("repo.pulls.wrong_commit_id"))
default:
ctx.ServerError("MergedManually", err)
- return
}
+
+ return
}
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
return
}
@@ -1213,15 +1265,14 @@ func MergePullRequest(ctx *context.Context) {
} else if scheduled {
// nothing more to do ...
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
- ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
+ ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
return
}
}
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
if models.IsErrInvalidMergeStyle(err) {
- ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
- ctx.Redirect(issue.Link())
+ ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
} else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1234,7 +1285,7 @@ func MergePullRequest(ctx *context.Context) {
return
}
ctx.Flash.Error(flashError)
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1247,19 +1298,19 @@ func MergePullRequest(ctx *context.Context) {
return
}
ctx.Flash.Error(flashError)
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if models.IsErrMergeUnrelatedHistories(err) {
log.Debug("MergeUnrelatedHistories error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if git.IsErrPushOutOfDate(err) {
log.Debug("MergePushOutOfDate error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date"))
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if models.IsErrSHADoesNotMatch(err) {
log.Debug("MergeHeadOutOfDate error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.head_out_of_date"))
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if git.IsErrPushRejected(err) {
log.Debug("MergePushRejected error: %v", err)
pushrejErr := err.(*git.ErrPushRejected)
@@ -1278,7 +1329,7 @@ func MergePullRequest(ctx *context.Context) {
}
ctx.Flash.Error(flashError)
}
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else {
ctx.ServerError("Merge", err)
}
@@ -1287,7 +1338,7 @@ func MergePullRequest(ctx *context.Context) {
log.Trace("Pull request merged: %d", pr.ID)
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
- ctx.ServerError("CreateOrStopIssueStopwatch", err)
+ ctx.ServerError("stopTimerIfAvailable", err)
return
}
@@ -1301,7 +1352,7 @@ func MergePullRequest(ctx *context.Context) {
return
}
if exist {
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
return
}
@@ -1309,9 +1360,9 @@ func MergePullRequest(ctx *context.Context) {
if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
headRepo = ctx.Repo.GitRepo
} else {
- headRepo, err = git.OpenRepository(ctx, pr.HeadRepo.RepoPath())
+ headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
if err != nil {
- ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
+ ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
return
}
defer headRepo.Close()
@@ -1319,7 +1370,7 @@ func MergePullRequest(ctx *context.Context) {
deleteBranch(ctx, pr, headRepo)
}
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
}
// CancelAutoMergePullRequest cancels a scheduled pr
@@ -1379,7 +1430,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
- labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
+ labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, true)
if ctx.Written() {
return
}
@@ -1457,6 +1508,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
+ if projectID > 0 {
+ if !ctx.Repo.CanWrite(unit.TypeProjects) {
+ ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
+ return
+ }
+ if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
+ ctx.ServerError("ChangeProjectAssign", err)
+ return
+ }
+ }
+
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
ctx.JSONRedirect(pullIssue.Link())
}
@@ -1521,9 +1583,9 @@ func CleanUpPullRequest(ctx *context.Context) {
gitBaseRepo = ctx.Repo.GitRepo
} else {
// If not just open it
- gitBaseRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
+ gitBaseRepo, err = gitrepo.OpenRepository(ctx, pr.BaseRepo)
if err != nil {
- ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err)
+ ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.FullName()), err)
return
}
defer gitBaseRepo.Close()
@@ -1536,9 +1598,9 @@ func CleanUpPullRequest(ctx *context.Context) {
gitRepo = ctx.Repo.GitRepo
} else if pr.BaseRepoID != pr.HeadRepoID {
// Otherwise just load it up
- gitRepo, err = git.OpenRepository(ctx, pr.HeadRepo.RepoPath())
+ gitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
if err != nil {
- ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
+ ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
return
}
defer gitRepo.Close()
@@ -1571,6 +1633,12 @@ func CleanUpPullRequest(ctx *context.Context) {
func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) {
fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch
+
+ if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
+ ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
+ return
+ }
+
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil {
switch {
case git.IsErrBranchNotExist(err):
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index 1359af9d3bf..64212291e16 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -11,18 +11,21 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
pull_model "code.gitea.io/gitea/models/pull"
"code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
pull_service "code.gitea.io/gitea/services/pull"
)
const (
- tplConversation base.TplName = "repo/diff/conversation"
- tplNewComment base.TplName = "repo/diff/new_comment"
+ tplDiffConversation base.TplName = "repo/diff/conversation"
+ tplConversationOutdated base.TplName = "repo/diff/conversation_outdated"
+ tplTimelineConversation base.TplName = "repo/issue/view_content/conversation"
+ tplNewComment base.TplName = "repo/diff/new_comment"
)
// RenderNewCodeCommentForm will render the form for creating a new review comment
@@ -48,6 +51,8 @@ func RenderNewCodeCommentForm(ctx *context.Context) {
return
}
ctx.Data["AfterCommitID"] = pullHeadCommitID
+ ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
+ upload.AddUploadContext(ctx, "comment")
ctx.HTML(http.StatusOK, tplNewComment)
}
@@ -73,6 +78,11 @@ func CreateCodeComment(ctx *context.Context) {
signedLine *= -1
}
+ var attachments []string
+ if setting.Attachment.Enabled {
+ attachments = form.Files
+ }
+
comment, err := pull_service.CreateCodeComment(ctx,
ctx.Doer,
ctx.Repo.GitRepo,
@@ -83,6 +93,7 @@ func CreateCodeComment(ctx *context.Context) {
!form.SingleReview,
form.Reply,
form.LatestCommitID,
+ attachments,
)
if err != nil {
ctx.ServerError("CreateCodeComment", err)
@@ -97,11 +108,7 @@ func CreateCodeComment(ctx *context.Context) {
log.Trace("Comment created: %-v #%d[%d] Comment[%d]", ctx.Repo.Repository, issue.Index, issue.ID, comment.ID)
- if form.Origin == "diff" {
- renderConversation(ctx, comment)
- return
- }
- ctx.Redirect(comment.Link(ctx))
+ renderConversation(ctx, comment, form.Origin)
}
// UpdateResolveConversation add or remove an Conversation resolved mark
@@ -152,22 +159,39 @@ func UpdateResolveConversation(ctx *context.Context) {
return
}
- if origin == "diff" {
- renderConversation(ctx, comment)
- return
- }
- ctx.JSONOK()
+ renderConversation(ctx, comment, origin)
}
-func renderConversation(ctx *context.Context, comment *issues_model.Comment) {
- comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line, ctx.Data["ShowOutdatedComments"].(bool))
+func renderConversation(ctx *context.Context, comment *issues_model.Comment, origin string) {
+ ctx.Data["PageIsPullFiles"] = origin == "diff"
+
+ showOutdatedComments := origin == "timeline" || ctx.Data["ShowOutdatedComments"].(bool)
+ comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line, showOutdatedComments)
if err != nil {
ctx.ServerError("FetchCodeCommentsByLine", err)
return
}
- ctx.Data["PageIsPullFiles"] = true
+ if len(comments) == 0 {
+ // if the comments are empty (deleted, outdated, etc), it's better to tell the users that it is outdated
+ ctx.HTML(http.StatusOK, tplConversationOutdated)
+ return
+ }
+
+ for _, c := range comments {
+ if err := c.LoadAttachments(ctx); err != nil {
+ ctx.ServerError("LoadAttachments", err)
+ return
+ }
+ }
+
+ ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
+ upload.AddUploadContext(ctx, "comment")
+
ctx.Data["comments"] = comments
- ctx.Data["CanMarkConversation"] = true
+ if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, comment.Issue, ctx.Doer); err != nil {
+ ctx.ServerError("CanMarkConversation", err)
+ return
+ }
ctx.Data["Issue"] = comment.Issue
if err = comment.Issue.LoadPullRequest(ctx); err != nil {
ctx.ServerError("comment.Issue.LoadPullRequest", err)
@@ -179,7 +203,13 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment) {
return
}
ctx.Data["AfterCommitID"] = pullHeadCommitID
- ctx.HTML(http.StatusOK, tplConversation)
+ if origin == "diff" {
+ ctx.HTML(http.StatusOK, tplDiffConversation)
+ } else if origin == "timeline" {
+ ctx.HTML(http.StatusOK, tplTimelineConversation)
+ } else {
+ ctx.Error(http.StatusBadRequest, "Unknown origin: "+origin)
+ }
}
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
@@ -209,9 +239,9 @@ func SubmitReview(ctx *context.Context) {
if issue.IsPoster(ctx.Doer.ID) {
var translated string
if reviewType == issues_model.ReviewTypeApprove {
- translated = ctx.Tr("repo.issues.review.self.approval")
+ translated = ctx.Locale.TrString("repo.issues.review.self.approval")
} else {
- translated = ctx.Tr("repo.issues.review.self.rejection")
+ translated = ctx.Locale.TrString("repo.issues.review.self.rejection")
}
ctx.Flash.Error(translated)
diff --git a/routers/web/repo/pull_review_test.go b/routers/web/repo/pull_review_test.go
new file mode 100644
index 00000000000..5f035f1eb04
--- /dev/null
+++ b/routers/web/repo/pull_review_test.go
@@ -0,0 +1,76 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "net/http/httptest"
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/contexttest"
+ "code.gitea.io/gitea/services/pull"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRenderConversation(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+
+ pr, _ := issues_model.GetPullRequestByID(db.DefaultContext, 2)
+ _ = pr.LoadIssue(db.DefaultContext)
+ _ = pr.Issue.LoadPoster(db.DefaultContext)
+ _ = pr.Issue.LoadRepo(db.DefaultContext)
+
+ run := func(name string, cb func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder)) {
+ t.Run(name, func(t *testing.T) {
+ ctx, resp := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
+ contexttest.LoadUser(t, ctx, pr.Issue.PosterID)
+ contexttest.LoadRepo(t, ctx, pr.BaseRepoID)
+ contexttest.LoadGitRepo(t, ctx)
+ defer ctx.Repo.GitRepo.Close()
+ cb(t, ctx, resp)
+ })
+ }
+
+ var preparedComment *issues_model.Comment
+ run("prepare", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
+ comment, err := pull.CreateCodeComment(ctx, pr.Issue.Poster, ctx.Repo.GitRepo, pr.Issue, 1, "content", "", false, 0, pr.HeadCommitID, nil)
+ if !assert.NoError(t, err) {
+ return
+ }
+ comment.Invalidated = true
+ err = issues_model.UpdateCommentInvalidate(ctx, comment)
+ if !assert.NoError(t, err) {
+ return
+ }
+ preparedComment = comment
+ })
+ if !assert.NotNil(t, preparedComment) {
+ return
+ }
+ run("diff with outdated", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
+ ctx.Data["ShowOutdatedComments"] = true
+ renderConversation(ctx, preparedComment, "diff")
+ assert.Contains(t, resp.Body.String(), `