Compare commits

..

2 Commits

Author SHA1 Message Date
Lunny Xiao
a12b5b3640 Add release notes for 1.24.0rc0 (#34305) 2025-04-29 21:45:47 +00:00
Giteabot
834dad8cef Fix the ci build (#34309) (#34310)
Backport #34309 by @lunny

Fix
https://github.com/go-gitea/gitea/actions/runs/14722306878/job/41318217870

A fork has been created under https://gitea.com/gitea/go-xsd-duration

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-04-28 22:48:55 -07:00
2097 changed files with 46874 additions and 57470 deletions

View File

@@ -1,19 +1,15 @@
{ {
"name": "Gitea DevContainer", "name": "Gitea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie", "image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
"containerEnv": {
// override "local" from packaged version
"GOTOOLCHAIN": "auto"
},
"features": { "features": {
// installs nodejs into container // installs nodejs into container
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "latest" "version": "20"
}, },
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {}, "ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": { "ghcr.io/devcontainers/features/python:1": {
"version": "3.13" "version": "3.12"
}, },
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
}, },

View File

@@ -36,6 +36,15 @@ _testmain.go
coverage.all coverage.all
cpu.out cpu.out
/modules/migration/bindata.go
/modules/migration/bindata.go.hash
/modules/options/bindata.go
/modules/options/bindata.go.hash
/modules/public/bindata.go
/modules/public/bindata.go.hash
/modules/templates/bindata.go
/modules/templates/bindata.go.hash
*.db *.db
*.log *.log
@@ -65,7 +74,6 @@ cpu.out
/yarn.lock /yarn.lock
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*
/pnpm-debug.log*
/public/assets/js /public/assets/js
/public/assets/css /public/assets/css
/public/assets/fonts /public/assets/fonts

View File

@@ -25,10 +25,6 @@ insert_final_newline = false
[templates/user/auth/oidc_wellknown.tmpl] [templates/user/auth/oidc_wellknown.tmpl]
indent_style = space indent_style = space
[templates/shared/actions/runner_badge_*.tmpl]
# editconfig lint requires these XML-like files to have charset defined, but the files don't have.
charset = unset
[Makefile] [Makefile]
indent_style = tab indent_style = tab

1004
.eslintrc.cjs Normal file

File diff suppressed because it is too large Load Diff

14
.github/labeler.yml vendored
View File

@@ -59,9 +59,9 @@ modifies/dependencies:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- "package.json" - "package.json"
- "pnpm-lock.yaml" - "package-lock.json"
- "pyproject.toml" - "pyproject.toml"
- "uv.lock" - "poetry.lock"
- "go.mod" - "go.mod"
- "go.sum" - "go.sum"
@@ -81,13 +81,3 @@ docs-update-needed:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- "custom/conf/app.example.ini" - "custom/conf/app.example.ini"
topic/code-linting:
- changed-files:
- any-glob-to-any-file:
- ".eslintrc.cjs"
- ".golangci.yml"
- ".markdownlint.yaml"
- ".spectral.yaml"
- ".yamllint.yaml"
- "stylelint.config.js"

View File

@@ -11,7 +11,7 @@ jobs:
if: github.repository == 'go-gitea/gitea' if: github.repository == 'go-gitea/gitea'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true

View File

@@ -58,7 +58,7 @@ jobs:
- "tools/*.ts" - "tools/*.ts"
- "assets/emoji.json" - "assets/emoji.json"
- "package.json" - "package.json"
- "pnpm-lock.yaml" - "package-lock.json"
- "Makefile" - "Makefile"
- ".eslintrc.cjs" - ".eslintrc.cjs"
- ".npmrc" - ".npmrc"
@@ -67,7 +67,7 @@ jobs:
- "**/*.md" - "**/*.md"
- ".markdownlint.yaml" - ".markdownlint.yaml"
- "package.json" - "package.json"
- "pnpm-lock.yaml" - "package-lock.json"
actions: actions:
- ".github/workflows/*" - ".github/workflows/*"
@@ -77,7 +77,7 @@ jobs:
- "tools/lint-templates-*.js" - "tools/lint-templates-*.js"
- "templates/**/*.tmpl" - "templates/**/*.tmpl"
- "pyproject.toml" - "pyproject.toml"
- "uv.lock" - "poetry.lock"
docker: docker:
- "Dockerfile" - "Dockerfile"
@@ -90,7 +90,7 @@ jobs:
- "templates/swagger/v1_input.json" - "templates/swagger/v1_input.json"
- "Makefile" - "Makefile"
- "package.json" - "package.json"
- "pnpm-lock.yaml" - "package-lock.json"
- ".spectral.yaml" - ".spectral.yaml"
yaml: yaml:
@@ -98,3 +98,4 @@ jobs:
- "**/*.yaml" - "**/*.yaml"
- ".yamllint.yaml" - ".yamllint.yaml"
- "pyproject.toml" - "pyproject.toml"
- "poetry.lock"

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -32,12 +32,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6 - uses: actions/setup-python@v5
- run: uv python install 3.12
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 python-version: "3.12"
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: pip install poetry
- run: make deps-py - run: make deps-py
- run: make deps-frontend - run: make deps-frontend
- run: make lint-templates - run: make lint-templates
@@ -48,8 +51,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6 - uses: actions/setup-python@v5
- run: uv python install 3.12 with:
python-version: "3.12"
- run: pip install poetry
- run: make deps-py - run: make deps-py
- run: make lint-yaml - run: make lint-yaml
@@ -59,10 +64,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-swagger - run: make lint-swagger
@@ -72,7 +78,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -84,7 +90,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -101,7 +107,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -116,7 +122,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -129,10 +135,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-frontend - run: make lint-frontend
- run: make checks-frontend - run: make checks-frontend
@@ -145,7 +152,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -177,10 +184,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-md - run: make lint-md
@@ -190,7 +198,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
pgsql: pgsql:
image: postgres:14 image: postgres:12
env: env:
POSTGRES_DB: test POSTGRES_DB: test
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
@@ -31,7 +31,7 @@ jobs:
minio: minio:
# as github actions doesn't support "entrypoint", we need to use a non-official image # as github actions doesn't support "entrypoint", we need to use a non-official image
# that has a custom entrypoint set to "minio server /data" # that has a custom entrypoint set to "minio server /data"
image: bitnamilegacy/minio:2023.8.31 image: bitnami/minio:2023.8.31
env: env:
MINIO_ROOT_USER: 123456 MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678 MINIO_ROOT_PASSWORD: 12345678
@@ -39,7 +39,7 @@ jobs:
- "9000:9000" - "9000:9000"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -113,7 +113,7 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
minio: minio:
image: bitnamilegacy/minio:2021.3.17 image: bitnami/minio:2021.3.17
env: env:
MINIO_ACCESS_KEY: 123456 MINIO_ACCESS_KEY: 123456
MINIO_SECRET_KEY: 12345678 MINIO_SECRET_KEY: 12345678
@@ -125,7 +125,7 @@ jobs:
- 10000:10000 - 10000:10000
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -155,7 +155,7 @@ jobs:
services: services:
mysql: mysql:
# the bitnami mysql image has more options than the official one, it's easier to customize # the bitnami mysql image has more options than the official one, it's easier to customize
image: bitnamilegacy/mysql:8.0 image: bitnami/mysql:8.0
env: env:
ALLOW_EMPTY_PASSWORD: true ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: testgitea MYSQL_DATABASE: testgitea
@@ -178,7 +178,7 @@ jobs:
- "993:993" - "993:993"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -218,7 +218,7 @@ jobs:
- 10000:10000 - 10000:10000
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true

View File

@@ -19,16 +19,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend frontend deps-backend - run: make deps-frontend frontend deps-backend
- run: pnpm exec playwright install --with-deps - run: npx playwright install --with-deps
- run: make test-e2e-sqlite - run: make test-e2e-sqlite
timeout-minutes: 40 timeout-minutes: 40
env: env:

View File

@@ -16,14 +16,15 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # 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 # 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 - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release
@@ -65,7 +66,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # 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 # 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 - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -74,6 +75,11 @@ jobs:
- name: Get cleaned branch name - name: Get cleaned branch name
id: clean_name id: clean_name
run: | run: |
# if main then say nightly otherwise cleanup name
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "branch=nightly" >> "$GITHUB_OUTPUT"
exit 0
fi
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: Login to Docker Hub - name: Login to Docker Hub
@@ -107,7 +113,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # 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 # 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 - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
@@ -116,6 +122,11 @@ jobs:
- name: Get cleaned branch name - name: Get cleaned branch name
id: clean_name id: clean_name
run: | run: |
# if main then say nightly otherwise cleanup name
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "branch=nightly" >> "$GITHUB_OUTPUT"
exit 0
fi
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: Login to Docker Hub - name: Login to Docker Hub

View File

@@ -17,14 +17,15 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # 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 # 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 - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release

View File

@@ -21,14 +21,15 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # 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 # 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 - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with: with:
node-version: 24 node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release

28
.gitignore vendored
View File

@@ -22,9 +22,6 @@ _test
.vscode .vscode
__debug_bin* __debug_bin*
# Visual Studio
/.vs/
*.cgo1.go *.cgo1.go
*.cgo2.c *.cgo2.c
_cgo_defun.c _cgo_defun.c
@@ -42,10 +39,14 @@ _testmain.go
coverage.all coverage.all
cpu.out cpu.out
/modules/migration/bindata.* /modules/migration/bindata.go
/modules/options/bindata.* /modules/migration/bindata.go.hash
/modules/public/bindata.* /modules/options/bindata.go
/modules/templates/bindata.* /modules/options/bindata.go.hash
/modules/public/bindata.go
/modules/public/bindata.go.hash
/modules/templates/bindata.go
/modules/templates/bindata.go.hash
*.db *.db
*.log *.log
@@ -78,7 +79,6 @@ cpu.out
/yarn.lock /yarn.lock
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*
/.pnpm-store
/public/assets/js /public/assets/js
/public/assets/css /public/assets/css
/public/assets/fonts /public/assets/fonts
@@ -110,15 +110,3 @@ prime/
# Manpage # Manpage
/man /man
# Ignore AI/LLM instruction files
/.claude/
/.cursorrules
/.cursor/
/.goosehints
/.windsurfrules
/.github/copilot-instructions.md
/AGENT.md
/CLAUDE.md
/llms.txt

View File

@@ -45,13 +45,7 @@ linters:
desc: do not use the ini package, use gitea's config system instead desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache - pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system desc: do not use the go-chi cache package, use gitea's cache system
nolintlint:
allow-unused: false
require-explanation: true
require-specific: true
gocritic: gocritic:
enabled-checks:
- equalFold
disabled-checks: disabled-checks:
- ifElseChain - ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way. - singleCaseSwitch # Every time this occurred in the code, there was no other way.
@@ -89,10 +83,6 @@ linters:
- name: unreachable-code - name: unreachable-code
- name: var-declaration - name: var-declaration
- name: var-naming - name: var-naming
arguments:
- [] # AllowList - do not remove as args for the rule are positional and won't work without lists first
- [] # DenyList
- - skip-package-name-checks: true # supress errors from underscore in migration packages
staticcheck: staticcheck:
checks: checks:
- all - all

View File

@@ -1,6 +1,9 @@
*.min.css *.min.css
*.min.js *.min.js
/assets/*.json /assets/*.json
/modules/options/bindata.go
/modules/public/bindata.go
/modules/templates/bindata.go
/options/gitignore /options/gitignore
/options/license /options/license
/public/assets /public/assets

5
.npmrc
View File

@@ -1,7 +1,6 @@
audit=false audit=false
fund=false fund=false
update-notifier=false update-notifier=false
package-lock=true
save-exact=true save-exact=true
auto-install-peers=true lockfile-version=3
dedupe-peer-dependents=false
enable-pre-post-scripts=true

View File

@@ -4,421 +4,7 @@ This changelog goes through the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has 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). been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.25.3](https://github.com/go-gitea/gitea/releases/tag/1.25.3) - 2025-12-17 ## [1.24.0-rc0](https://github.com/go-gitea/gitea/releases/tag/1.24.0-rc0) - 2025-04-28
* SECURITY
* Bump toolchain to go1.25.5, misc fixes (#36082)
* ENHANCEMENTS
* Add strikethrough button to markdown editor (#36087) (#36104)
* Add "site admin" back to profile menu (#36010) (#36013)
* Improve math rendering (#36124) (#36125)
* BUGFIXES
* Check user visibility when redirecting to a renamed user (#36148) (#36159)
* Fix various bugs (#36139) (#36151)
* Fix bug when viewing the commit diff page with non-ANSI files (#36149) (#36150)
* Hide RSS icon when viewing a file not under a branch (#36135) (#36141)
* Fix SVG size calulation, only use `style` attribute (#36133) (#36134)
* Make Golang correctly delete temp files during uploading (#36128) (#36129)
* Fix the bug when ssh clone with redirect user or repository (#36039) (#36090)
* Use Golang net/smtp instead of gomail's smtp to send email (#36055) (#36083)
* Fix edit user email bug in API (#36068) (#36081)
* Fix bug when updating user email (#36058) (#36066)
* Fix incorrect viewed files counter if file has changed (#36009) (#36047)
* Fix container registry error handling (#36021) (#36037)
* Fix webAuthn insecure error view (#36165) (#36179)
* Fix some file icon ui (#36078) (#36088)
* Fix Actions `pull_request.paths` being triggered incorrectly by rebase (#36045) (#36054)
* Fix error handling in mailer and wiki services (#36041) (#36053)
* Fix bugs when comparing and creating pull request (#36166) (#36144)
## [1.25.2](https://github.com/go-gitea/gitea/releases/tag/1.25.2) - 2025-11-23
* SECURITY
* Upgrade golang.org/x/crypto to 0.45.0 (#35985) (#35988)
* Fix various permission & login related bugs (#36002) (#36004)
* ENHANCEMENTS
* Display source code downloads last for release attachments (#35897) (#35903)
* Change project default column icon to 'star' (#35967) (#35979)
* BUGFIXES
* Allow empty commit when merging pull request with squash style (#35989) (#36003)
* Fix container push tag overwriting (#35936) (#35954)
* Fix corrupted external render content (#35946) and upgrade golang.org/x packages (#35950)
* Limit reading bytes instead of ReadAll (#35928) (#35934)
* Use correct form field for allowed force push users in branch protection API (#35894) (#35908)
* Fix team member access check (#35899) (#35905)
* Fix conda null depend issue (#35900) (#35902)
* Set the dates to now when not specified by the caller (#35861) (#35874)
* Fix gogit ListEntriesRecursiveWithSize (#35862)
* Misc CSS fixes (#35888) (#35981)
* Don't show unnecessary error message to end users for DeleteBranchAfterMerge (#35937) (#35941)
* Load jQuery as early as possible to support custom scripts (#35926) (#35929)
* Allow to display embed images/pdfs when SERVE_DIRECT was enabled on MinIO storage (#35882) (#35917)
* Make OAuth2 issuer configurable (#35915) (#35916)
* Fix #35763: Add proper page title for project pages (#35773) (#35909)
* Fix avatar upload error handling (#35887) (#35890)
* Contribution heatmap improvements (#35876) (#35880)
* Remove padding override on `.ui .sha.label` (#35864) (#35873)
* Fix pull description code label background (#35865) (#35870)
## [1.25.1](https://github.com/go-gitea/gitea/releases/tag/v1.25.1) - 2025-11-03
* BUGFIXES
* Make ACME email optional (#35849) #35857
* Add a doctor command to fix inconsistent run status (#35840) (#35845)
* Remove wrong code (#35846)
* Fix viewed files number is not right if not all files loaded (#35821) (#35844)
* Fix incorrect pull request counter (#35819) (#35841)
* Upgrade go mail to 0.7.2 and fix the bug (#35833) (#35837)
* Revert gomail to v0.7.0 to fix sending mail failed (#35816) (#35824)
* Fix clone mixed bug (#35810) (#35822)
* Fix cli "Before" handling (#35797) (#35808)
* Improve and fix markup code preview rendering (#35777) (#35787)
* Fix actions rerun bug (#35783) (#35784)
* Fix actions schedule update issue (#35767) (#35774)
* Fix circular spin animation direction (#35785) (#35823)
* Fix file extension on gogs.png (#35793) (#35799)
* Add pnpm to Snapcraft (#35778)
## [1.25.0](https://github.com/go-gitea/gitea/releases/tag/v1.25.0) - 2025-10-30
* BREAKING
* Return 201 Created for CreateVariable API responses (#34517)
* Add label 'state' to metric 'gitea_users' (#34326)
* SECURITY
* Upgrade security public key (#34956)
* Also include all security fixes in 1.24.x after 1.25.0-rc0
* FEATURES
* Stream repo zip/tar.gz/bundle achives by default (#35487)
* Use configurable remote name for git commands (#35172)
* Send email on Workflow Run Success/Failure (#34982)
* Refactor OpenIDConnect to support SSH/FullName sync (#34978)
* Refactor repo contents API and add "contents-ext" API (#34822)
* Add support for 3D/CAD file formats preview (#34794)
* Improve instance wide ssh commit signing (#34341)
* Edit file workflow for creating a fork and proposing changes (#34240)
* Follow file symlinks in the UI to their target (#28835)
* Allow renaming/moving binary/LFS files in the UI (#34350)
* PERFORMANCE
* Improve the performance when detecting the file editable (#34653)
* ENHANCEMENTS
* Enable more markdown paste features in textarea editor (#35494)
* Don't store repo archives on `gitea dump` (#35467)
* Always return the relevant status information, even if no status exists. (#35335)
* Add start time on perf trace because it seems some steps haven't been recorded. (#35282)
* Remove deprecated auth sources (#35272)
* When sorting issues by nearest due date, issues without due date should be sorted ascending (#35267)
* Disable field count validation of CSV viewer (#35228)
* Add `has_code` to repository REST API (#35214)
* Display pull request in merged commit view (#35202)
* Support Basic Authentication for archive downloads (#35087)
* Add hover background to table rows in user and repo admin page (#35072)
* Partially refresh notifications list (#35010)
* Also display "recently pushed branch" alert on PR view (#35001)
* Refactor time tracker UI (#34983)
* Improve CLI commands (#34973)
* Improve project & label color picker and image scroll (#34971)
* Improve NuGet API Parity (#21291) (#34940)
* Support getting last commit message using contents-ext API (#34904)
* Adds title on branch commit counts (#34869)
* Add "Cancel workflow run" button to Actions list page (#34817)
* Improve img lazy loading (#34804)
* Forks repository list page follow other repositories page (#34784)
* Add ff_only parameter to POST /repos/{owner}/{repo}/merge-upstream (#34770)
* Rework delete org and rename org UI (#34762)
* Improve nuget/rubygems package registries (#34741)
* Add repo file tree item link behavior (#34730)
* Add issue delete notifier (#34592)
* Improve Actions list (#34530)
* Add a default tab on repo header when migrating (#34503)
* Add post-installation redirect based on admin account status (#34493)
* Trigger 'unlabeled' event when label is Deleted from PR (#34316)
* Support annotated tags when using create release API (#31840)
* Use lfs label for lfs file rather than a long description (#34363)
* Add "View workflow file" to Actions list page (#34538)
* Move organization's visibility change to danger zone. (#34814)
* Don't block site admin's operation if SECRET_KEY is lost (#35721)
* Make restricted users can access public repositories (#35693)
* The status icon of the Action step is consistent with GitHub (#35618) #35621
* BUGFIXES
* Update tab title when navigating file tree (#35757) #35772
* Fix "ref-issue" handling in markup (#35739) #35771
* Fix webhook to prevent tag events from bypassing branch filters targets (#35567) #35577
* Fix markup init after issue comment editing (#35536) #35537
* Fix creating pull request failure when the target branch name is the same as some tag (#35552) #35582
* Fix auto-expand and auto-scroll for actions logs (#35570) (#35583) #35586
* Use inputs context when parsing workflows (#35590) #35595
* Fix diffpatch API endpoint (#35610) #35613
* Creating push comments before invoke pull request checking (#35647) #35668
* Fix missing Close when error occurs and abused connection pool (#35658) #35670
* Fix build (#35674)
* Use LFS object size instead of blob size when viewing a LFS file (#35679)
* Fix workflow run event status while rerunning a failed job (#35689)
* Avoid emoji mismatch and allow to only enable chosen emojis (#35692)
* Refactor legacy code, fix LFS auth bypass, fix symlink bypass (#35708)
* Fix various trivial problems (#35714)
* Fix attachment file size limit in server backend (#35519)
* Honor delete branch on merge repo setting when using merge API (#35488)
* Fix external render, make iframe render work (#35727, #35730)
* Upgrade go mail to 0.7.2 (#35748)
* Revert #18491, fix oauth2 client link account (#35745)
* Fix different behavior in status check pattern matching with double stars (#35474)
* Fix overflow in notifications list (#35446)
* Fix package link setting can only list limited repositories (#35394)
* Extend comment treepath length (#35389)
* Fix font-size in inline code comment preview (#35209)
* Move git config/remote to gitrepo package and add global lock to resolve possible conflict when updating repository git config file (#35151)
* Change some columns from text to longtext and fix column wrong type caused by xorm (#35141)
* Redirect to a presigned URL of HEAD for HEAD requests (#35088)
* Fix git commit committer parsing and add some tests (#35007)
* Fix OCI manifest parser (#34797)
* Refactor FindOrgOptions to use enum instead of bool, fix membership visibility (#34629)
* Fix notification count positioning for variable-width elements (#34597)
* Keeping consistent between UI and API about combined commit status state and fix some bugs (#34562)
* Fix possible panic (#34508)
* Fix autofocus behavior (#34397)
* Fix Actions API (#35204)
* Fix ListWorkflowRuns OpenAPI response model. (#35026)
* Small fix in Pull Requests page (#34612)
* Fix http auth header parsing (#34936)
* Fix modal + form abuse (#34921)
* Fix PR toggle WIP (#34920)
* Fix log fmt (#34810)
* Replace stopwatch toggle with explicit start/stop actions (#34818)
* Fix some package registry problems (#34759)
* Fix RPM package download routing & missing package version count (#34909)
* Fix repo search input height (#34330)
* Fix "The sidebar of the repository file list does not have a fixed height #34298" (#34321)
* Fix minor typos in two files #HSFDPMUW (#34944)
* Fix actions skipped commit status indicator (#34507)
* Fix job status aggregation logic (#35000)
* Fix broken OneDev migration caused by various REST API changes in OneDev 7.8.0 and later (#35216)
* Fix typo in oauth2_full_name_claim_name string (#35199)
* Fix typo in locale_en-US.ini (#35196)
* API
* Exposing TimeEstimate field in the API (#35475)
* UpdateBranch API supports renaming a branch (#35374)
* Add `owner` and `parent` fields clarification to docs (#35023)
* Improve OAuth2 provider (correct Issuer, respect ENABLED) (#34966)
* Add a `login`/`login-name`/`username` disambiguation to affected endpoint parameters and response/request models (#34901)
* Do not mutate incoming options to SearchRepositoryByName (#34553)
* Do not mutate incoming options to RenderUserSearch and SearchUsers (#34544)
* Export repo's manual merge settings (#34502)
* Add date range filtering to commit retrieval endpoints (#34497)
* Add endpoint deleting workflow run (#34337)
* Add workflow_run api + webhook (#33964)
* REFACTOR
* Move updateref and removeref to gitrepo and remove unnecessary open repository (#35511)
* Remove unused param `doer` (#34545)
* Split GetLatestCommitStatus as two functions (#34535)
* Use gitrepo.SetDefaultBranch when set default branch of wiki repository (#33911)
* Refactor editor (#34780)
* Refactor packages (#34777)
* Refactor container package (#34877)
* Refactor "change file" API (#34855)
* Rename pull request GetGitRefName to GetGitHeadRefName to prepare introducing GetGitMergeRefName (#35093)
* Move git command to git/gitcmd (#35483)
* Use db.WithTx/WithTx2 instead of TxContext when possible (#35428)
* Support Node.js 22.6 with type stripping (#35427)
* Migrate tools and configs to typescript, require node.js >= 22.18.0 (#35421)
* Check user and repo for redirects when using git via SSH transport (#35416)
* Remove the duplicated function GetTags (#35375)
* Refactor to use reflect.TypeFor (#35370)
* Deleting branch could delete broken branch which has database record but git branch is missing (#35360)
* Exit with success when already up to date (#35312)
* Split admin config settings templates to make it maintain easier (#35294)
* A small refactor to use context in the service layer (#35179)
* Refactor and update mail templates (#35150)
* Use db.WithTx/WithTx2 instead of TxContext when possible (#35130)
* Align `issue-title-buttons` with `list-header` (#35018)
* Add Notifications section in User Settings (#35008)
* Tweak placement of diff file menu (#34999)
* Refactor mail template and support preview (#34990)
* Rerun job only when run is done (#34970)
* Merge index.js (#34963)
* Refactor "delete-button" to "link-action" (#34962)
* Refactor webhook and fix feishu/lark secret (#34961)
* Exclude devtest.ts from tailwindcss (#34935)
* Refactor head navbar icons (#34922)
* Improve html escape (#34911)
* Improve tags list page (#34898)
* Improve `labels-list` rendering (#34846)
* Remove unused variable HUGO_VERSION (#34840)
* Correct migration tab name (#34826)
* Refactor template helper (#34819)
* Use `shallowRef` instead of `ref` in `.vue` files where possible (#34813)
* Use standalone function to update repository cols (#34811)
* Refactor wiki (#34805)
* Remove unnecessary duplicate code (#34733)
* Refactor embedded assets and drop unnecessary dependencies (#34692)
* Update x/crypto package and make builtin SSH use default parameters (#34667)
* Add `--color-logo`, matching the logo's primary color (#34639)
* Add openssh-keygen to rootless image (#34625)
* Replace update repository function in some places (#34566)
* Change "rejected" to "changes requested" in 3rd party PR review notification (#34481)
* Remove legacy template helper functions (#34426)
* Use run-name and evaluate workflow variables (#34301)
* Move HasWiki to repository service package (#33912)
* Move some functions from package git to gitrepo (#33910)
* TESTING
* Add webhook test for push event (#34442)
* Add a webhook push test for dev branch (#34421)
* Add migrations tests (#34456) (#34498)
* STYLE
* Enforce explanation for necessary nolints and fix bugs (#34883)
* Fix remaining issues after `gopls modernize` formatting (#34771)
* Update gofumpt, add go.mod ignore directive (#35434)
* Enforce nolint scope (#34851)
* Enable gocritic `equalFold` and fix issues (#34952)
* Run `gopls modernize` on codebase (#34751)
* Upgrade `gopls` to v0.19.0, add `make fix` (#34772)
* BUILD
* bump archives&rar dep (#35637) #35638
* Use github.com/mholt/archives replace github.com/mholt/archiver (#35390)
* Update JS and PY dependencies (#35444)
* Upgrade devcontainer go version to 1.24.6 (#35298)
* Upgrade golang to 1.25.1 and add descriptions for the swagger structs' fields (#35418)
* Update JS and PY deps (#35191)
* Update JS and PY dependencies (#34391)
* Update go tool dependencies (#34845)
* Update `uint8-to-base64`, remove type stub (#34844)
* Switch to `@resvg/resvg-wasm` for `generate-images` (#35415)
* Switch to pnpm (#35274)
* Update chroma to v2.20.0 (#35220)
* Migrate to urfave v3 (#34510)
* Update JS deps, regenerate SVGs (#34640)
* Upgrade dependencies (#35384)
* Bump `@github/relative-time-element` to v4.4.8 (#34413)
* Update JS dependencies (#34951)
* Upgrade orgmode to v1.8.0 (#34721)
* Raise minimum Node.js version to 20, test on 24 (#34713)
* Update JS deps (#34701)
* Upgrade htmx to 2.0.6 (#34887)
* Update eslint to v9 (#35485)
* Update js dependencies (#35429)
* Clean up npm dependencies (#35508)
* Clean up npm dependencies (#35484)
* Bump setup-node to v5 (#35448)
* MISC
* Add gitignore rules to exclude LLM instruction files (#35076)
* Gitignore: Visual Studio settings folder (#34375)
* Improve language in en-US locale strings (#35124)
* Fixed all grammatical errors in locale_en-US.ini (#35053)
* Docs/fix typo and grammar in CONTRIBUTING.md (#35024)
* Improve english grammar and readability in locale_en-US.ini (#35017)
## [1.24.7](https://github.com/go-gitea/gitea/releases/tag/v1.24.7) - 2025-10-24
* SECURITY
* Refactor legacy code (#35708) (#35713)
* Fixing issue #35530: Password Leak in Log Messages (#35584) (#35665)
* Fix a bug missed return (#35655) (#35671)
* BUGFIXES
* Fix inputing review comment will remove reviewer (#35591) (#35664)
* TESTING
* Mock external service in hcaptcha TestCaptcha (#35604) (#35663)
* Fix build (#35669)
## [1.24.6](https://github.com/go-gitea/gitea/releases/tag/v1.24.6) - 2025-09-10
* SECURITY
* Upgrade xz to v0.5.15 (#35385)
* BUGFIXES
* Fix a compare page 404 bug when the pull request disabled (#35441) (#35453)
* Fix bug when issue disabled, pull request number in the commit message cannot be redirected (#35420) (#35442)
* Add author.name field to Swift Package Registry API response (#35410) (#35431)
* Remove usernames when empty in discord webhook (#35412) (#35417)
* Allow foreachref parser to grow its buffer (#35365) (#35376)
* Allow deleting comment with content via API like web did (#35346) (#35354)
* Fix atom/rss mixed error (#35345) (#35347)
* Fix review request webhook bug (#35339)
* Remove duplicate html IDs (#35210) (#35325)
* Fix LFS range size header response (#35277) (#35293)
* Fix GitHub release assets URL validation (#35287) (#35290)
* Fix token lifetime, closes #35230 (#35271) (#35281)
* Fix push commits comments when changing the pull request target branch (#35386) (#35443)
## [1.24.5](https://github.com/go-gitea/gitea/releases/tag/v1.24.5) - 2025-08-12
* BUGFIXES
* Fix a bug where lfs gc never worked. (#35198) (#35255)
* Reload issue when sending webhook to make num comments is right. (#35243) (#35248)
* Fix bug when review pull request commits (#35192) (#35246)
* MISC
* Vertically center "Show Resolved" (#35211) (#35218)
## [1.24.4](https://github.com/go-gitea/gitea/releases/tag/v1.24.4) - 2025-08-03
* BUGFIXES
* Fix various bugs (1.24) (#35186)
* Fix migrate input box bug (#35166) (#35171)
* Only hide dropzone when no files have been uploaded (#35156) (#35167)
* Fix review comment/dimiss comment x reference can be refereced back (#35094) (#35099)
* Fix submodule nil check (#35096) (#35098)
* MISC
* Don't use full-file highlight when there is a git diff textconv (#35114) (#35119)
* Increase gap on latest commit (#35104) (#35113)
## [1.24.3](https://github.com/go-gitea/gitea/releases/tag/v1.24.3) - 2025-07-15
* BUGFIXES
* Fix form property assignment edge case (#35073) (#35078)
* Improve submodule relative path handling (#35056) (#35075)
* Fix incorrect comment diff hunk parsing, fix github asset ID nil panic (#35046) (#35055)
* Fix updating user visibility (#35036) (#35044)
* Support base64-encoded agit push options (#35037) (#35041)
* Make submodule link work with relative path (#35034) (#35038)
* Fix bug when displaying git user avatar in commits list (#35006)
* Fix API response for swagger spec (#35029)
* Start automerge check again after the conflict check and the schedule (#34988) (#35002)
* Fix the response format for actions/workflows (#35009) (#35016)
* Fix repo settings and protocol log problems (#35012) (#35013)
* Fix project images scroll (#34971) (#34972)
* Mark old reviews as stale on agit pr updates (#34933) (#34965)
* Fix git graph page (#34948) (#34949)
* Don't send trigger for a pending review's comment create/update/delete (#34928) (#34939)
* Fix some log and UI problems (#34863) (#34868)
* Fix archive API (#34853) (#34857)
* Ignore force pushes for changed files in a PR review (#34837) (#34843)
* Fix SSH LFS timeout (#34838) (#34842)
* Fix team permissions (#34827) (#34836)
* Fix job status aggregation logic (#34823) (#34835)
* Fix issue filter (#34914) (#34915)
* Fix typo in pull request merge warning message text (#34899) (#34903)
* Support the open-icon of folder (#34168) (#34896)
* Optimize flex layout of release attachment area (#34885) (#34886)
* Fix the issue of abnormal interface when there is no issue-item on the project page (#34791) (#34880)
* Skip updating timestamp when sync branch (#34875)
* Fix required contexts and commit status matching bug (#34815) (#34829)
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/v1.24.2) - 2025-06-20
* BUGFIXES
* Fix container range bug (#34795) (#34796)
* Upgrade chi to v5.2.2 (#34798) (#34799)
* BUILD
* Bump poetry feature to new url for dev container (#34787) (#34790)
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/v1.24.1) - 2025-06-18
* ENHANCEMENTS
* Improve alignment of commit status icon on commit page (#34750) (#34757)
* Support title and body query parameters for new PRs (#34537) (#34752)
* BUGFIXES
* When using rules to delete packages, remove unclean bugs (#34632) (#34761)
* Fix ghost user in feeds when pushing in an actions, it should be gitea-actions (#34703) (#34756)
* Prevent double markdown link brackets when pasting URL (#34745) (#34748)
* Prevent duplicate form submissions when creating forks (#34714) (#34735)
* Fix markdown wrap (#34697) (#34702)
* Fix pull requests API convert panic when head repository is deleted. (#34685) (#34687)
* Fix commit message rendering and some UI problems (#34680) (#34683)
* Fix container range bug (#34725) (#34732)
* Fix incorrect cli default values (#34765) (#34766)
* Fix dropdown filter (#34708) (#34711)
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
* Fix tag target (#34781) #34783
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/v1.24.0) - 2025-05-26
* BREAKING * BREAKING
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076) * Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
@@ -455,7 +41,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Improve pull request list API performance (#34052) * Improve pull request list API performance (#34052)
* Cache GPG keys, emails and users when list commits (#34086) * Cache GPG keys, emails and users when list commits (#34086)
* Refactor Git Attribute & performance optimization (#34154) * Refactor Git Attribute & performance optimization (#34154)
* Performance optimization for tags synchronization (#34355) #34522
* ENHANCEMENTS * ENHANCEMENTS
* Code * Code
@@ -482,7 +67,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Workflow_dispatch use workflow from trigger branch (#33098) * Workflow_dispatch use workflow from trigger branch (#33098)
* Add action auto-scroll (#30057) * Add action auto-scroll (#30057)
* Add workflow_job webhook (#33694) * Add workflow_job webhook (#33694)
* Add a button editing action secret (#34462)
* Pull Request * Pull Request
* Auto expand "New PR" form (#33971) * Auto expand "New PR" form (#33971)
@@ -496,7 +80,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Show warning on navigation if currently editing comment or title (#32920) * Show warning on navigation if currently editing comment or title (#32920)
* Make tracked time representation display as hours (#33315) * Make tracked time representation display as hours (#33315)
* Add No Results Prompt Message on Issue List Page (#33699) * Add No Results Prompt Message on Issue List Page (#33699)
* Add sort option recentclose for issues and pulls (#34525) #34539
* Packages * Packages
* Link to nuget dependencies (#26554) * Link to nuget dependencies (#26554)
@@ -633,28 +216,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Simplify context ref name (#33267) * Simplify context ref name (#33267)
* BUGFIXES * BUGFIXES
* Fix some dropdown problems on the issue sidebar (#34308) #34327
* Do not return archive download URLs in API if downloads are disabled (#34324) #34338
* Fix LFS files being editable in web UI (#34356) #34362
* Fix only text/* being viewable in web UI (#34374) #34378
* Fix LFS file not stored in LFS when uploaded/edited via API or web UI (#34367)
* Grey out expired artifact on Artifacts list (#34314) #34404
* Fix incorrect divergence cache after switching default branch (#34370) #34406
* Refactor commit message rendering and fix bugs (#34412) #34414
* Merge and tweak markup editor expander CSS (#34409) #34415
* Fix GetUsersByEmails (#34423) #34425
* Only git operations should update last changed of a repository (#34388) #34427
* Fix comment textarea scroll issue in Firefox (#34438) #34446
* Fix repo broken check (#34444) #34452
* Fix remove org user failure on mssql (#34449) #34453
* Fix Workflow run Not Found page (#34459) #34466
* When updating comment, if the content is the same, just return and not update the database (#34422) #34464
* Fix project board view (#34470) #34475
* Fix get / delete runner to use consistent http 404 and 500 status (#34480) #34488
* Fix url validation in webhook add/edit API (#34492) #34496
* Fix edithook api can not update package, status and workflow_job events (#34495) #34499
* Fix ephemeral runner deletion (#34447) #34513
* Don't display error log when .git-blame-ignore-revs doesn't exist (#34457)
* Only allow admins to rename default/protected branches (#33276) * Only allow admins to rename default/protected branches (#33276)
* Improve "lock conversation" UI (#34207) * Improve "lock conversation" UI (#34207)
* Fix incorrect file links (#34189) * Fix incorrect file links (#34189)
@@ -690,37 +251,8 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix dynamic content loading init problem (#33748) * Fix dynamic content loading init problem (#33748)
* Fix git empty check and HEAD request (#33690) * Fix git empty check and HEAD request (#33690)
* Fix Untranslated Text on Actions Page (#33635) * Fix Untranslated Text on Actions Page (#33635)
* Fix issue label delete incorrect labels webhook payload (#34575)
* Fix incorrect page navigation with up and down arrow on last item of dashboard repos (#34570)
* Fix/improve avatar sync from LDAP (#34573)
* Fix some trivial problems (#34579)
* Retain issue sort type when a keyword search is introduced (#34559)
* Always use an empty line to separate the commit message and trailer (#34512)
* Fix line-button issue after file selection in file tree (#34574)
* Fix doctor deleting orphaned issues attachments (#34142)
* Add webhook assigning test and fix possible bug (#34420)
* Fix possible nil description of pull request when migrating from CodeCommit (#34541)
* Refactor commit reader (#34542)
* Fix possible pull request broken when leave the page immediately after clicking the update button #34509
* Ignore "Close" error when uploading container blob (#34620)
* Fix missed merge commit sha and time when migrating from codecommit (#34645)
* Fix GetUsersByEmails (#34643)
* Misc CSS fixes (#34638)
* Add codecommit to supported services in api docs (#34626)
* Validate hex colors when creating/editing labels (#34623)
* Fix possible pull request broken when leave the page immediately after clicking the update button (#34509)
* Fix margin issue in markup paragraph rendering (#34599)
* Fix migration pull request title too long (#34577)
* Fix footnote jump behavior on the issue page. (#34621)
* Fix "oras" OCI client compatibility (#34666)
* Fix last admin check when syncing users (#34649)
* Fix skip paths check on tag push events in workflows (#34602) #34670
* MISC * MISC
* Bump to alpine 3.22 (#34613)
* Make pull request and issue history more compact (#34588)
* Run integration tests against postgres 14 (#34514) #34536
* Enable addtional linters (#34085) * Enable addtional linters (#34085)
* Enable testifylint rules (#34075) * Enable testifylint rules (#34075)
* Enable staticcheck QFxxxx rules (#34064) * Enable staticcheck QFxxxx rules (#34064)
@@ -786,60 +318,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix eslint (#33002) * Fix eslint (#33002)
* Update JS dependencies (#32914) * Update JS dependencies (#32914)
* Bump x/net (#32896) (#32900) * Bump x/net (#32896) (#32900)
* Only activity tab needs heatmap data loading (#34652)
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/v1.23.8) - 2025-05-11
* SECURITY
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
* Update net package (#34228) (#34232)
* BUGFIXES
* Fix releases sidebar navigation link (#34436) #34439
* Fix bug webhook milestone is not right. (#34419) #34429
* Fix two missed null value checks on the wiki page. (#34205) (#34215)
* Swift files can be passed either as file or as form value (#34068) (#34236)
* Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
* Upgrade github v61 -> v71 to fix migrating bug (#34389)
* Fix bug when visiting comparation page (#34334) (#34364)
* Fix wrong review requests when updating the pull request (#34286) (#34304)
* Fix github migration error when using multiple tokens (#34144) (#34302)
* Explicitly not update indexes when sync database schemas (#34281) (#34295)
* Fix panic when comment is nil (#34257) (#34277)
* Fix project board links to related Pull Requests (#34213) (#34222)
* Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
* DOCUMENTATION
* Update token creation API swagger documentation (#34288) (#34296)
* MISC
* Fix CI Build (#34315)
* Add riscv64 support (#34199) (#34204)
* Bump go version in go.mod (#34160)
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/v1.23.7) - 2025-04-07
* Enhancements
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
* Also check default ssh-cert location for host (#34099) (#34100) (#34116)
* BUGFIXES
* Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
* Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120)
* Fix invalid version in RPM package path (#34112) (#34115)
* Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
* Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
* Try to fix check-attr bug (#34029) (#34033)
* Git client will follow 301 but 307 (#34005) (#34010)
* Fix block expensive for 1.23 (#34127)
* Fix markdown frontmatter rendering (#34102) (#34107)
* Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
* Do not show 500 error when default branch doesn't exist (#34096) (#34097)
* Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
* Simplify emoji rendering (#34048) (#34049)
* Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
* Pull request updates will also trigger code owners review requests (#33744) (#34045)
* Fix org repo creation being limited by user limits (#34030) (#34044)
* Fix git client accessing renamed repo (#34034) (#34043)
* Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036)
* Polyfill WeakRef (#34025) (#34028)
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24 ## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
@@ -913,7 +391,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* BUGFIXES * BUGFIXES
* Fix a bug caused by status webhook template #33512 * Fix a bug caused by status webhook template #33512
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/v1.23.2) - 2025-02-04 ## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
* BREAKING * BREAKING
* Add tests for webhook and fix some webhook bugs (#33396) (#33442) * Add tests for webhook and fix some webhook bugs (#33396) (#33442)
@@ -3443,7 +2921,7 @@ Key highlights of this release encompass significant changes categorized under `
* Improve decryption failure message (#24573) (#24575) * Improve decryption failure message (#24573) (#24575)
* Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572) * Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572)
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/v1.19.3) - 2023-05-03 ## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/1.19.3) - 2023-05-03
* SECURITY * SECURITY
* Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400 * Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400
@@ -3456,7 +2934,7 @@ Key highlights of this release encompass significant changes categorized under `
* Fix incorrect CurrentUser check for docker rootless (#24435) * Fix incorrect CurrentUser check for docker rootless (#24435)
* Getting the tag list does not require being signed in (#24413) (#24416) * Getting the tag list does not require being signed in (#24413) (#24416)
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/v1.19.2) - 2023-04-26 ## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/1.19.2) - 2023-04-26
* SECURITY * SECURITY
* Require repo scope for PATs for private repos and basic authentication (#24362) (#24364) * Require repo scope for PATs for private repos and basic authentication (#24362) (#24364)
@@ -3955,7 +3433,7 @@ Key highlights of this release encompass significant changes categorized under `
* Display attachments of review comment when comment content is blank (#23035) (#23046) * Display attachments of review comment when comment content is blank (#23035) (#23046)
* Return empty url for submodule tree entries (#23043) (#23048) * Return empty url for submodule tree entries (#23043) (#23048)
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/v1.18.4) - 2023-02-20 ## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
* SECURITY * SECURITY
* Provide the ability to set password hash algorithm parameters (#22942) (#22943) * Provide the ability to set password hash algorithm parameters (#22942) (#22943)
@@ -4382,7 +3860,7 @@ Key highlights of this release encompass significant changes categorized under `
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867) * Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
* Fix UI mis-align for PR commit history (#20845) (#20859) * Fix UI mis-align for PR commit history (#20845) (#20859)
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/v1.17.1) - 2022-08-17 ## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
* SECURITY * SECURITY
* Correctly escape within tribute.js (#20831) (#20832) * Correctly escape within tribute.js (#20831) (#20832)

View File

@@ -30,7 +30,7 @@ These are the values to which people in the Gitea community should aspire.
- **Be constructive.** - **Be constructive.**
- Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation. - Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation.
- Avoid unconstructive criticism: don't merely decry the current state of affairs; offer—or at least solicit—suggestions as to how things may be improved. - Avoid unconstructive criticism: don't merely decry the current state of affairs; offer—or at least solicit—suggestions as to how things may be improved.
- Avoid snarking (pithy, unproductive, sniping comments). - Avoid snarking (pithy, unproductive, sniping comments)
- Avoid discussing potentially offensive or sensitive issues; this all too often leads to unnecessary conflict. - Avoid discussing potentially offensive or sensitive issues; this all too often leads to unnecessary conflict.
- Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults to a person or group). - Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults to a person or group).
- **Be responsible.** - **Be responsible.**
@@ -42,7 +42,7 @@ People are complicated. You should expect to be misunderstood and to misundersta
### Our Pledge ### Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
### Our Standards ### Our Standards

View File

@@ -591,7 +591,7 @@ be reviewed by two maintainers and must pass the automatic tests.
## Releasing Gitea ## Releasing Gitea
- Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future. - Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody is against it in about several hours. - Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours.
- If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps: - If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
- Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`. - Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
- When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin` - When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`

View File

@@ -1,5 +1,5 @@
# Build stage # Build stage
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env FROM docker.io/library/golang:1.24-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}
@@ -15,7 +15,6 @@ RUN apk --no-cache add \
git \ git \
nodejs \ nodejs \
npm \ npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# Setup repo # Setup repo
@@ -40,8 +39,9 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/etc/s6/.s6-svscan/* \ /tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea \ /go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini /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.22 FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000 EXPOSE 22 3000
@@ -83,3 +83,4 @@ CMD ["/usr/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local / COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh

View File

@@ -1,5 +1,5 @@
# Build stage # Build stage
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env FROM docker.io/library/golang:1.24-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}
@@ -15,7 +15,6 @@ RUN apk --no-cache add \
git \ git \
nodejs \ nodejs \
npm \ npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# Setup repo # Setup repo
@@ -38,8 +37,9 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/tmp/local/usr/local/bin/gitea \ /tmp/local/usr/local/bin/gitea \
/go/src/code.gitea.io/gitea/gitea \ /go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini /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.22 FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000 EXPOSE 2222 3000
@@ -52,7 +52,6 @@ RUN apk --no-cache add \
git \ git \
curl \ curl \
gnupg \ gnupg \
openssh-keygen \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
@@ -72,6 +71,7 @@ RUN chown git:git /var/lib/gitea /etc/gitea
COPY --from=build-env /tmp/local / COPY --from=build-env /tmp/local /
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
# git:git # git:git
USER 1000:1000 USER 1000:1000

View File

@@ -36,7 +36,9 @@ a1012112796 <1012112796@qq.com> (@a1012112796)
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise) Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
Norwin Roosen <git@nroo.de> (@noerw) Norwin Roosen <git@nroo.de> (@noerw)
Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu) Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu)
Patrick Schratz <patrick.schratz@gmail.com> (@pat-s)
Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R) Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R)
Steven Kriegler <sk.bunsenbrenner@gmail.com> (@justusbunsi)
Jimmy Praet <jimmy.praet@telenet.be> (@jpraet) Jimmy Praet <jimmy.praet@telenet.be> (@jpraet)
Leon Hofmeister <dev.lh@web.de> (@delvh) Leon Hofmeister <dev.lh@web.de> (@delvh)
Wim <wim@42.be> (@42wim) Wim <wim@42.be> (@42wim)
@@ -62,5 +64,3 @@ Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
hiifong <i@hiif.ong> (@hiifong) hiifong <i@hiif.ong> (@hiifong)
metiftikci <metiftikci@hotmail.com> (@metiftikci) metiftikci <metiftikci@hotmail.com> (@metiftikci)
Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX) Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)
TheFox <thefox0x7@gmail.com> (@TheFox0x7)

168
Makefile
View File

@@ -23,21 +23,20 @@ SHASUM ?= shasum -a 256
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
COMMA := , COMMA := ,
XGO_VERSION := go-1.25.x XGO_VERSION := go-1.24.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf00e56953556c6d80f8a01b286 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1 GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.8 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0 GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1
GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.20.0
DOCKER_IMAGE ?= gitea/gitea DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest DOCKER_TAG ?= latest
@@ -48,17 +47,6 @@ ifeq ($(HAS_GO), yes)
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS) CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
endif endif
CGO_ENABLED ?= 0
ifneq (,$(findstring sqlite,$(TAGS))$(findstring pam,$(TAGS)))
CGO_ENABLED = 1
endif
STATIC ?=
EXTLDFLAGS ?=
ifneq ($(STATIC),)
EXTLDFLAGS = -extldflags "-static"
endif
ifeq ($(GOOS),windows) ifeq ($(GOOS),windows)
IS_WINDOWS := yes IS_WINDOWS := yes
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows) else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
@@ -92,19 +80,11 @@ ifeq ($(RACE_ENABLED),true)
endif endif
STORED_VERSION_FILE := VERSION STORED_VERSION_FILE := VERSION
HUGO_VERSION ?= 0.111.3
GITHUB_REF_TYPE ?= branch GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
# Enable typescript support in Node.js before 22.18
# TODO: Remove this once we can raise the minimum Node.js version to 22.18 (alpine >= 3.23)
NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | tr '.' ' '))
ifeq ($(shell test "$(NODE_VERSION)" -lt "022018000"; echo $$?),0)
NODE_VARS := NODE_OPTIONS="--experimental-strip-types"
else
NODE_VARS :=
endif
ifneq ($(GITHUB_REF_TYPE),branch) ifneq ($(GITHUB_REF_TYPE),branch)
VERSION ?= $(subst v,,$(GITHUB_REF_NAME)) VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
GITEA_VERSION ?= $(VERSION) GITEA_VERSION ?= $(VERSION)
@@ -136,11 +116,12 @@ GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/m
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.ts tailwind.config.ts WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.* BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go
@@ -162,14 +143,20 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST)
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.ts tests/e2e ESLINT_FILES := web_src/js tools *.js *.ts *.cjs tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*)) SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.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) GO_SOURCES += $(GENERATED_GO_DEST)
GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
GO_SOURCES += $(BINDATA_DEST)
GENERATED_GO_DEST += $(BINDATA_DEST)
endif
# Force installation of playwright dependencies by setting this flag # Force installation of playwright dependencies by setting this flag
ifdef DEPS_PLAYWRIGHT ifdef DEPS_PLAYWRIGHT
@@ -226,13 +213,10 @@ git-check:
node-check: node-check:
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p')) $(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' '))) $(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
$(eval PNPM_MISSING := $(shell hash pnpm > /dev/null 2>&1 || echo 1)) $(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" ]; then \ $(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater to build. You can get it at https://nodejs.org/en/download/"; \ @if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
exit 1; \ echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
fi
@if [ "$(PNPM_MISSING)" = "1" ]; then \
echo "Gitea requires pnpm to build. You can install it at https://pnpm.io/installation"; \
exit 1; \ exit 1; \
fi fi
@@ -242,7 +226,7 @@ clean-all: clean ## delete backend, frontend and integration files
.PHONY: clean .PHONY: clean
clean: ## delete backend and integration files clean: ## delete backend and integration files
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \ rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
integrations*.test \ integrations*.test \
e2e*.test \ e2e*.test \
tests/integration/gitea-integration-* \ tests/integration/gitea-integration-* \
@@ -253,7 +237,7 @@ clean: ## delete backend and integration files
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
.PHONY: fmt .PHONY: fmt
fmt: ## format the Go and template code fmt: ## format the Go code
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' @GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl')) $(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only @# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
@@ -272,19 +256,6 @@ fmt-check: fmt
exit 1; \ exit 1; \
fi fi
.PHONY: fix
fix: ## apply automated fixes to Go code
$(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./...
.PHONY: fix-check
fix-check: fix
@diff=$$(git diff --color=always $(GO_SOURCES)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fix' and commit the result:"; \
printf "%s" "$${diff}"; \
exit 1; \
fi
.PHONY: $(TAGS_EVIDENCE) .PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE): $(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR) @mkdir -p $(MAKE_EVIDENCE_DIR)
@@ -297,7 +268,7 @@ endif
.PHONY: generate-swagger .PHONY: generate-swagger
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT) $(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) $(SWAGGER_SPEC_INPUT)
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)' $(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
.PHONY: swagger-check .PHONY: swagger-check
@@ -324,7 +295,7 @@ checks: checks-frontend checks-backend ## run various consistency checks
checks-frontend: lockfile-check svg-check ## check frontend files checks-frontend: lockfile-check svg-check ## check frontend files
.PHONY: checks-backend .PHONY: checks-backend
checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint-spell ## lint everything lint: lint-frontend lint-backend lint-spell ## lint everything
@@ -346,29 +317,29 @@ lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backen
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules ## lint js files lint-js: node_modules ## lint js files
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
$(NODE_VARS) pnpm exec vue-tsc npx vue-tsc
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules ## lint js files and fix issues lint-js-fix: node_modules ## lint js files and fix issues
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) --fix npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
$(NODE_VARS) pnpm exec vue-tsc npx vue-tsc
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules ## lint css files lint-css: node_modules ## lint css files
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) npx stylelint --color --max-warnings=0 $(STYLELINT_FILES)
.PHONY: lint-css-fix .PHONY: lint-css-fix
lint-css-fix: node_modules ## lint css files and fix issues lint-css-fix: node_modules ## lint css files and fix issues
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
.PHONY: lint-swagger .PHONY: lint-swagger
lint-swagger: node_modules ## lint swagger files lint-swagger: node_modules ## lint swagger files
$(NODE_VARS) pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC) npx spectral lint -q -F hint $(SWAGGER_SPEC)
.PHONY: lint-md .PHONY: lint-md
lint-md: node_modules ## lint markdown files lint-md: node_modules ## lint markdown files
$(NODE_VARS) pnpm exec markdownlint *.md npx markdownlint *.md
.PHONY: lint-spell .PHONY: lint-spell
lint-spell: ## lint spelling lint-spell: ## lint spelling
@@ -402,7 +373,7 @@ lint-go-gitea-vet: ## lint go files with gitea-vet
.PHONY: lint-go-gopls .PHONY: lint-go-gopls
lint-go-gopls: ## lint go files with gopls lint-go-gopls: ## lint go files with gopls
@echo "Running gopls check..." @echo "Running gopls check..."
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES) @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA)
.PHONY: lint-editorconfig .PHONY: lint-editorconfig
lint-editorconfig: lint-editorconfig:
@@ -415,12 +386,12 @@ lint-actions: ## lint action workflow files
.PHONY: lint-templates .PHONY: lint-templates
lint-templates: .venv node_modules ## lint template files lint-templates: .venv node_modules ## lint template files
@node tools/lint-templates-svg.ts @node tools/lint-templates-svg.js
@uv run --frozen djlint $(shell find templates -type f -iname '*.tmpl') @poetry run djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-yaml .PHONY: lint-yaml
lint-yaml: .venv ## lint yaml files lint-yaml: .venv ## lint yaml files
@uv run --frozen yamllint -s . @poetry run yamllint -s .
.PHONY: watch .PHONY: watch
watch: ## watch everything and continuously rebuild watch: ## watch everything and continuously rebuild
@@ -429,7 +400,7 @@ watch: ## watch everything and continuously rebuild
.PHONY: watch-frontend .PHONY: watch-frontend
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development $(NODE_VARS) pnpm exec webpack --watch --progress --disable-interpret NODE_ENV=development npx webpack --watch --progress
.PHONY: watch-backend .PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild watch-backend: go-check ## watch backend files and continuously rebuild
@@ -445,7 +416,7 @@ test-backend: ## test backend files
.PHONY: test-frontend .PHONY: test-frontend
test-frontend: node_modules ## test frontend files test-frontend: node_modules ## test frontend files
$(NODE_VARS) pnpm exec vitest npx vitest
.PHONY: test-check .PHONY: test-check
test-check: test-check:
@@ -588,7 +559,7 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright .PHONY: playwright
playwright: deps-frontend playwright: deps-frontend
$(NODE_VARS) pnpm exec playwright install $(PLAYWRIGHT_FLAGS) npx playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e% .PHONY: test-e2e%
test-e2e%: TEST_TYPE ?= e2e test-e2e%: TEST_TYPE ?= e2e
@@ -769,10 +740,7 @@ security-check:
go run $(GOVULNCHECK_PACKAGE) -show color ./... go run $(GOVULNCHECK_PACKAGE) -show color ./...
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),) CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
$(error pam support set via TAGS doesn't support static builds)
endif
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
.PHONY: release .PHONY: release
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
@@ -848,15 +816,14 @@ deps-tools: ## install tool dependencies
$(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(GOVULNCHECK_PACKAGE) & \
$(GO) install $(ACTIONLINT_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE) & \
$(GO) install $(GOPLS_PACKAGE) & \ $(GO) install $(GOPLS_PACKAGE) & \
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
wait wait
node_modules: pnpm-lock.yaml node_modules: package-lock.json
$(NODE_VARS) pnpm install --frozen-lockfile npm install --no-save
@touch node_modules @touch node_modules
.venv: uv.lock .venv: poetry.lock
uv sync poetry install
@touch .venv @touch .venv
.PHONY: update .PHONY: update
@@ -864,34 +831,34 @@ update: update-js update-py ## update js and py dependencies
.PHONY: update-js .PHONY: update-js
update-js: node-check | node_modules ## update js dependencies update-js: node-check | node_modules ## update js dependencies
$(NODE_VARS) pnpm exec updates -u -f package.json npx updates -u -f package.json
rm -rf node_modules pnpm-lock.yaml rm -rf node_modules package-lock.json
$(NODE_VARS) pnpm install npm install --package-lock
$(NODE_VARS) pnpm exec nolyfill install npx nolyfill install
$(NODE_VARS) pnpm install npm install --package-lock
@touch node_modules @touch node_modules
.PHONY: update-py .PHONY: update-py
update-py: node-check | node_modules ## update py dependencies update-py: node-check | node_modules ## update py dependencies
$(NODE_VARS) pnpm exec updates -u -f pyproject.toml npx updates -u -f pyproject.toml
rm -rf .venv uv.lock rm -rf .venv poetry.lock
uv sync poetry install
@touch .venv @touch .venv
.PHONY: webpack .PHONY: webpack
webpack: $(WEBPACK_DEST) ## build webpack files webpack: $(WEBPACK_DEST) ## build webpack files
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
@$(MAKE) -s node-check node_modules @$(MAKE) -s node-check node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(WEBPACK_DEST_ENTRIES)
@echo "Running webpack..." @echo "Running webpack..."
@BROWSERSLIST_IGNORE_OLD_DATA=true $(NODE_VARS) pnpm exec webpack --disable-interpret @BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
@touch $(WEBPACK_DEST) @touch $(WEBPACK_DEST)
.PHONY: svg .PHONY: svg
svg: node-check | node_modules ## build svg files svg: node-check | node_modules ## build svg files
rm -rf $(SVG_DEST_DIR) rm -rf $(SVG_DEST_DIR)
node tools/generate-svg.ts node tools/generate-svg.js
.PHONY: svg-check .PHONY: svg-check
svg-check: svg svg-check: svg
@@ -905,11 +872,11 @@ svg-check: svg
.PHONY: lockfile-check .PHONY: lockfile-check
lockfile-check: lockfile-check:
$(NODE_VARS) pnpm install --frozen-lockfile npm install --package-lock-only
@diff=$$(git diff --color=always pnpm-lock.yaml); \ @diff=$$(git diff --color=always package-lock.json); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "pnpm-lock.yaml is inconsistent with package.json"; \ echo "package-lock.json is inconsistent with package.json"; \
echo "Please run 'pnpm install --frozen-lockfile' and commit the result:"; \ echo "Please run 'npm install --package-lock-only' and commit the result:"; \
printf "%s" "$${diff}"; \ printf "%s" "$${diff}"; \
exit 1; \ exit 1; \
fi fi
@@ -929,8 +896,9 @@ generate-gitignore: ## update gitignore files
$(GO) run build/generate-gitignores.go $(GO) run build/generate-gitignores.go
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules ## generate images generate-images: | node_modules
cd tools && node generate-images.ts $(TAGS) npm install --no-save fabric@6 imagemin-zopfli@7
node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
generate-manpage: ## generate manpage generate-manpage: ## generate manpage

View File

@@ -52,7 +52,7 @@ or if SQLite support is required:
The `build` target is split into two sub-targets: The `build` target is split into two sub-targets:
- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod). - `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod).
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and [pnpm](https://pnpm.io/installation). - `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater.
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. 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.
@@ -80,9 +80,9 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com) [![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com)
Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language, ask one of the managers in the Crowdin project to add a new language there. Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
You can also just create an issue for adding a language or ask on Discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty, but we hope to fill it as questions pop up. You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up.
Get more information from [documentation](https://docs.gitea.com/contributing/localization). Get more information from [documentation](https://docs.gitea.com/contributing/localization).

View File

@@ -14,12 +14,12 @@ Please **DO NOT** file a public issue, instead send your report privately to `se
Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body. Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body.
The PGP key is valid until July 4, 2026. The PGP key is valid until July 9, 2025.
``` ```
Key ID: 6FCD2D5B Key ID: 6FCD2D5B
Key Type: RSA Key Type: RSA
Expires: 7/4/2026 Expires: 7/9/2025
Key Size: 4096/4096 Key Size: 4096/4096
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
``` ```
@@ -42,18 +42,18 @@ lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBAhsD tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBAhsD
BQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAFiEEPeA9HhRKfwaTWZncqv0jgW/N BQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAFiEEPeA9HhRKfwaTWZncqv0jgW/N
LVsFAmhoHmkFCQeT6esACgkQqv0jgW/NLVuFLRAAmjBQSKRAgs2bFIEj7HLAbDp4 LVsFAmaMse0FCQW4fW8ACgkQqv0jgW/NLVtXLg/+PF4G9Jhlui15BTNlEBJAV2P/
f+XkdH+GsT3jRPOZ9QZgmtM+TfoE4yNgIVfOl+s4RdjM/W4QzqZuPQ55hbEHd056 1QlAV2krk0fP7tykn0FR9RfGIfVV/kwC1f+ouosYPQDDevl9LWdUIM+g94DtNo2o
cJmm7B+6GsHFcdrPmh65sOCEIyh4+t45dUfeWpFsDPqm9j1UHXAJQIpB8vDEVAPH 7ACpcL3morvt5lVGpIZHL8TbX0qmFRXL/pB/cB+K6IwYvh2mrbp2zH+r4SCRyFYq
t+3wLCk8GMPJs1o5tIyMmaO23ngvkwn8eG7KgY+rp2PzObrb5g7ppci0ILzILkrp BjgXYFTI1MylJ1ShAjU6Z+m3oJ+2xs5LzHS0X6zkTjzA2Zl4zQzciQ9T+wJcE7Zi
HVjZsEfUWRgSVF7LuU5ppqDKrlcqwUpQq6n3kGMZcLrCp6ACKP04TBmTfUxNwdL7 HXdM1+YMF8KGNP8J9Rpug5oNDJ98lgZirRY7c3A/1xmYBiPnULwuuymdqEZO7l70
I0N7apI2Pbct9T1Gv/lYAUFWyU2c3gh/EBLbO6BukaLOFRQHrtNfdJV/YnMPlcXr SeAlE1RWYX8kbOBnBb/KY4XwE3Vic1oEzc9DiPWVH1ElX86WNNsFzuyULiwoBoWg
LUJjK9K4eAH9DsrZqrisz/LthsC2BaNIN3KRMTk5YTYgmIh8GXzSgihORmtDFELC pqZGhL9x1p5+46RGQSDczsHM7YGVtfYOiDo2PAVrmwsT0BnXnK8Oe3YIkvmUPEJu
RroID3pTuS0zjXh+wpY9GuPTh7UW23p42Daxca4fAT4k5EclvDRUrL21xMopPMiL OkLt0Z6A5n8pz8zhQzuApwBsK4ncJ8zTCpvz/pfKKqZC/Vnoh3gKGhDGvOZ+b5IJ
HuNdELz4FVchRTy05PjzKVyjVInDNojE2KUxnjxZDzYJ6aT/g+coD5yfntYm8BEj 0kUTe2JsbnwFixDUMDtacQ1op8XOyLoLVmgqLn0+Pws4XPBlMof2bioFir3yHKnP
+ZzL0ndZES54hzKLpv7zwBQwFzam68clZYmDPILOPTflQDfpGEWmJK4undFU5obz gNchsF1agrlSIo5GA8u4ga+IlCSfvFIKrl7+cxacKcJYt/vbOU5KcvVJI5EtHKCG
ZsQRz0R3ulspChATbZxO0d5LX2obLpKO9X3b5VoO1KF+R8Vjw1Y0KxrNZ6rIcfqH xfHjHY2ah1Qww7SxW6IXiRZZzPpsL2mBM2CD7N3qh9bV2s27wxYCdUodsIZbiyHe
Z50QVQKSe9dm08K0ON+5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen oWPzfBnkmiAN8KlZxHm5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46 GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
@@ -65,19 +65,19 @@ s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYCGwwWIQQ94D0eFEp/BpNZmdyq/SOBb80t Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYCGwwWIQQ94D0eFEp/BpNZmdyq/SOBb80t
WwUCaGgeJAUJB5PppgAKCRCq/SOBb80tW/NWEACB6Jrf0gWlk7e+hNCdnbM0ZVWU WwUCZoyyjQUJBbh+DwAKCRCq/SOBb80tW18XD/9MXztmf01MT+1kZdBouZ/7Rp/7
f2sHNFfXxxsdhpcDgKbNHtkZb8nZgv8AX+5fTtUwMVa3vKcdw30xFiIM5N7cCIPV 9kuqo//B1G+RXau4oFtPqb67kNe2WaIc3u5B73PUHsMf3i6z4ib2KbMhZZerLn0O
vg/5z5BtfEaitnabEUG2iiVDIy8IHXIcK10rX+7BosA3QDl2PsiBHwyi5G13lRk8 dRglcuPeNWmsASY3dH/XVG0cT0zvvWegagd12TJEl3Vs+7XNrOw4cwDj9L1+GH9m
zGTSNDuOalug33h5/lr2dPigamkq74Aoy29q8Rjad6GfWHipL2bFimgtY+Zdi0BH kSt4uaANWn/6a3RvMRhiVEYuNwhAzcKaactPmYqrLJgoVLbRSDkgyHaMQ2jKgLxk
NLk4EJXxj1SgVx5dtkQzWJReBA5M+FQ4QYQZBO+f4TDoOLmjui152uhkoLBQbGAa ifS/fvluGV0ub2Po6DJiqfRpd1tDvPhe9y1+r1WFDZsOcvTcZUfSt/7dXMGfqGu0
WWJFTVxm0bG5MXloEL3gA8DfU7XDwuW/sHJC5pBko8RpQViooOhckMepZV3Y83DK 2daVFlfeSXSALrDE5uc0UxodHCpP3sqRYDZevGLBRaaTkIjYXG/+N898+7K5WJF4
bwLYa3JmPgj2rEv4993dvrJbQhpGd082HOxOsllCs8pgNq1SnXpWYfcGTgGKC3ts xXOLWxM2cwGkG7eC9pugcDnBp9XlF7O+GBiZ05JUe5flXDQFZ+h3exjopu6KHF1B
U8YZUUJUQ7mi2L8Tv3ix20c9EiGmA30JAmA8eZTC3cWup91ZkkVBFRml2czTXajd RnzNy8LC0UKb+AuvRIOLV92a9Q9wGWU/jaVDu6nZ0umAeuSzxiHoDsonm0Fl9QAz
RWZ6GbHV5503ueDQcB8yBVgF3CSixs67+dGSbD3p86OqGrjAcJzM5TFbNKcnGLdE 2/xCokebuoeLrEK7R2af3X86mqq3sVO4ax+HPYChzOaVQBiHUW/TAldWcldYYphR
kGbZpNwAISy750lXzXKmyrh5RTCeTOQerbwCMBvHZO+HAevA/LXDTw2OAiSIQlP5 /e2WsbmQfvCRtz/bZfo+aUVnrHNjzVMtF2SszdVmA/04Y8pS28MqtuRqhm5DPOOd
sYA4sFYLQ30OAkgJcmdp/pSgVj/erNtSN07ClrOpDb/uFpQymO6K2h0Pst3feNVK g1YeUywK5jRZ1twyo1kzJEFPLaoeaXaycsR1PMVBW0Urik5mrR/pOWq7PPoZoKb2
9M2VbqL9C51z/wyHLg== lXYLE8bwkuQTmsyL1g==
=SfZA =9i7d
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
``` ```

103
assets/go-licenses.json generated

File diff suppressed because one or more lines are too long

View File

@@ -5,10 +5,19 @@
package main package main
// Libraries that are included to vendor utilities used during Makefile build. // Libraries that are included to vendor utilities used during build.
// These libraries will not be included in a normal compilation. // These libraries will not be included in a normal compilation.
import ( import (
// for embed
_ "github.com/shurcooL/vfsgen"
// for cover merge
_ "golang.org/x/tools/cover"
// for vet // for vet
_ "code.gitea.io/gitea-vet" _ "code.gitea.io/gitea-vet"
// for swagger
_ "github.com/go-swagger/go-swagger/cmd/swagger"
) )

View File

@@ -181,7 +181,7 @@ func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string
break break
} }
} }
return mainOptions, subCmd, subArgs return
} }
func showUsage() { func showUsage() {

View File

@@ -6,22 +6,87 @@
package main package main
import ( import (
"bytes"
"crypto/sha1"
"fmt" "fmt"
"log"
"net/http"
"os" "os"
"path/filepath"
"strconv"
"code.gitea.io/gitea/modules/assetfs" "github.com/shurcooL/vfsgen"
) )
func main() { func needsUpdate(dir, filename string) (bool, []byte) {
if len(os.Args) != 3 { needRegen := false
fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}") _, err := os.Stat(filename)
os.Exit(1) if err != nil {
needRegen = true
} }
dir, filename := os.Args[1], os.Args[2] oldHash, err := os.ReadFile(filename + ".hash")
fmt.Printf("generating bindata for %s to %s\n", dir, filename) if err != nil {
if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil { oldHash = []byte{}
fmt.Printf("failed: %s\n", err.Error())
os.Exit(1)
} }
hasher := sha1.New()
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
_, _ = hasher.Write([]byte(d.Name()))
_, _ = hasher.Write([]byte(info.ModTime().String()))
_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
return nil
})
if err != nil {
return true, oldHash
}
newHash := hasher.Sum([]byte{})
if bytes.Compare(oldHash, newHash) != 0 {
return true, newHash
}
return needRegen, newHash
}
func main() {
if len(os.Args) < 4 {
log.Fatal("Insufficient number of arguments. Need: directory packageName filename")
}
dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3]
var useGlobalModTime bool
if len(os.Args) == 5 {
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
}
update, newHash := needsUpdate(dir, filename)
if !update {
fmt.Printf("bindata for %s already up-to-date\n", packageName)
return
}
fmt.Printf("generating bindata for %s\n", packageName)
var fsTemplates http.FileSystem = http.Dir(dir)
err := vfsgen.Generate(fsTemplates, vfsgen.Options{
PackageName: packageName,
BuildTags: "bindata",
VariableName: "Assets",
Filename: filename,
UseGlobalModTime: useGlobalModTime,
})
if err != nil {
log.Fatalf("%v\n", err)
}
_ = os.WriteFile(filename+".hash", newHash, 0o666)
} }

View File

@@ -4,13 +4,12 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@@ -18,7 +17,7 @@ var (
CmdActions = &cli.Command{ CmdActions = &cli.Command{
Name: "actions", Name: "actions",
Usage: "Manage Gitea Actions", Usage: "Manage Gitea Actions",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken, subcmdActionsGenRunnerToken,
}, },
} }
@@ -39,7 +38,10 @@ var (
} }
) )
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { func runGenerateActionsRunnerToken(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setting.MustInstalled() setting.MustInstalled()
scope := c.String("scope") scope := c.String("scope")

View File

@@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@@ -23,7 +23,7 @@ var (
CmdAdmin = &cli.Command{ CmdAdmin = &cli.Command{
Name: "admin", Name: "admin",
Usage: "Perform common administrative operations", Usage: "Perform common administrative operations",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdUser, subcmdUser,
subcmdRepoSyncReleases, subcmdRepoSyncReleases,
subcmdRegenerate, subcmdRegenerate,
@@ -41,7 +41,7 @@ var (
subcmdRegenerate = &cli.Command{ subcmdRegenerate = &cli.Command{
Name: "regenerate", Name: "regenerate",
Usage: "Regenerate specific files", Usage: "Regenerate specific files",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdRegenHooks, microcmdRegenHooks,
microcmdRegenKeys, microcmdRegenKeys,
}, },
@@ -50,15 +50,15 @@ var (
subcmdAuth = &cli.Command{ subcmdAuth = &cli.Command{
Name: "auth", Name: "auth",
Usage: "Modify external auth providers", Usage: "Modify external auth providers",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdAuthAddOauth(), microcmdAuthAddOauth,
microcmdAuthUpdateOauth(), microcmdAuthUpdateOauth,
microcmdAuthAddLdapBindDn(), microcmdAuthAddLdapBindDn,
microcmdAuthUpdateLdapBindDn(), microcmdAuthUpdateLdapBindDn,
microcmdAuthAddLdapSimpleAuth(), microcmdAuthAddLdapSimpleAuth,
microcmdAuthUpdateLdapSimpleAuth(), microcmdAuthUpdateLdapSimpleAuth,
microcmdAuthAddSMTP(), microcmdAuthAddSMTP,
microcmdAuthUpdateSMTP(), microcmdAuthUpdateSMTP,
microcmdAuthList, microcmdAuthList,
microcmdAuthDelete, microcmdAuthDelete,
}, },
@@ -71,8 +71,8 @@ var (
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "title", Name: "title",
Usage: "a title of a message", Usage: `a title of a message`,
Required: true, Value: "",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "content", Name: "content",
@@ -86,27 +86,28 @@ var (
}, },
}, },
} }
)
func idFlag() *cli.Int64Flag { idFlag = &cli.Int64Flag{
return &cli.Int64Flag{
Name: "id", Name: "id",
Usage: "ID of authentication source", Usage: "ID of authentication source",
} }
} )
func runRepoSyncReleases(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
if err := git.InitSimple(); err != nil { if err := git.InitSimple(ctx); err != nil {
return err return err
} }
log.Trace("Synchronizing repository releases (this may take a while)") log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ { for page := 1; ; page++ {
repos, count, err := repo_model.SearchRepositoryByName(ctx, repo_model.SearchRepoOptions{ repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
PageSize: repo_model.RepositoryListDefaultPageSize, PageSize: repo_model.RepositoryListDefaultPageSize,
Page: page, Page: page,

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -14,14 +13,14 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
auth_service "code.gitea.io/gitea/services/auth" auth_service "code.gitea.io/gitea/services/auth"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
microcmdAuthDelete = &cli.Command{ microcmdAuthDelete = &cli.Command{
Name: "delete", Name: "delete",
Usage: "Delete specific auth source", Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag()}, Flags: []cli.Flag{idFlag},
Action: runDeleteAuth, Action: runDeleteAuth,
} }
microcmdAuthList = &cli.Command{ microcmdAuthList = &cli.Command{
@@ -57,7 +56,10 @@ var (
} }
) )
func runListAuth(ctx context.Context, c *cli.Command) error { func runListAuth(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
@@ -88,11 +90,14 @@ func runListAuth(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
func runDeleteAuth(ctx context.Context, c *cli.Command) error { func runDeleteAuth(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View File

@@ -12,7 +12,7 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
type ( type (
@@ -24,8 +24,8 @@ type (
} }
) )
func commonLdapCLIFlags() []cli.Flag { var (
return []cli.Flag{ commonLdapCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "Authentication name.", Usage: "Authentication name.",
@@ -103,10 +103,8 @@ func commonLdapCLIFlags() []cli.Flag {
Usage: "The attribute of the users LDAP record containing the users avatar.", Usage: "The attribute of the users LDAP record containing the users avatar.",
}, },
} }
}
func ldapBindDnCLIFlags() []cli.Flag { ldapBindDnCLIFlags = append(commonLdapCLIFlags,
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "bind-dn", Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.", Usage: "The DN to bind to the LDAP server with when searching for the user.",
@@ -159,59 +157,49 @@ func ldapBindDnCLIFlags() []cli.Flag {
Name: "group-team-map-removal", Name: "group-team-map-removal",
Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group", Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
}) })
}
func ldapSimpleAuthCLIFlags() []cli.Flag { ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "user-dn", Name: "user-dn",
Usage: "The user's DN.", Usage: "The user's DN.",
}) })
}
func microcmdAuthAddLdapBindDn() *cli.Command { microcmdAuthAddLdapBindDn = &cli.Command{
return &cli.Command{
Name: "add-ldap", Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source", Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().addLdapBindDn(ctx, cmd) return newAuthService().addLdapBindDn(c)
}, },
Flags: ldapBindDnCLIFlags(), Flags: ldapBindDnCLIFlags,
} }
}
func microcmdAuthUpdateLdapBindDn() *cli.Command { microcmdAuthUpdateLdapBindDn = &cli.Command{
return &cli.Command{
Name: "update-ldap", Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source", Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().updateLdapBindDn(ctx, cmd) return newAuthService().updateLdapBindDn(c)
}, },
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...), Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
} }
}
func microcmdAuthAddLdapSimpleAuth() *cli.Command { microcmdAuthAddLdapSimpleAuth = &cli.Command{
return &cli.Command{
Name: "add-ldap-simple", Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source", Usage: "Add new LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().addLdapSimpleAuth(ctx, cmd) return newAuthService().addLdapSimpleAuth(c)
}, },
Flags: ldapSimpleAuthCLIFlags(), Flags: ldapSimpleAuthCLIFlags,
} }
}
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
return &cli.Command{
Name: "update-ldap-simple", Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source", Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().updateLdapSimpleAuth(ctx, cmd) return newAuthService().updateLdapSimpleAuth(c)
}, },
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...), Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
} }
} )
// newAuthService creates a service with default functions. // newAuthService creates a service with default functions.
func newAuthService() *authService { func newAuthService() *authService {
@@ -224,7 +212,7 @@ func newAuthService() *authService {
} }
// parseAuthSourceLdap assigns values on authSource according to command line flags. // parseAuthSourceLdap assigns values on authSource according to command line flags.
func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) { func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) {
if c.IsSet("name") { if c.IsSet("name") {
authSource.Name = c.String("name") authSource.Name = c.String("name")
} }
@@ -244,7 +232,7 @@ func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) {
} }
// parseLdapConfig assigns values on config according to command line flags. // parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Command, config *ldap.Source) error { func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
if c.IsSet("name") { if c.IsSet("name") {
config.Name = c.String("name") config.Name = c.String("name")
} }
@@ -257,7 +245,7 @@ func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("security-protocol") { if c.IsSet("security-protocol") {
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol")) p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
if !ok { if !ok {
return fmt.Errorf("unknown security protocol name: %s", c.String("security-protocol")) return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
} }
config.SecurityProtocol = p config.SecurityProtocol = p
} }
@@ -349,27 +337,32 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
// getAuthSource gets the login source by its id defined in the command line flags. // getAuthSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type. // It returns an error if the id is not set, does not match any source or if the source is not of expected type.
func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) { func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) {
if err := argsSet(c, "id"); err != nil { if err := argsSet(c, "id"); err != nil {
return nil, err return nil, err
} }
authSource, err := a.getAuthSourceByID(ctx, c.Int64("id")) authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if authSource.Type != authType { if authSource.Type != authType {
return nil, fmt.Errorf("invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String()) return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
} }
return authSource, nil return authSource, nil
} }
// addLdapBindDn adds a new LDAP via Bind DN authentication source. // addLdapBindDn adds a new LDAP via Bind DN authentication source.
func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error { func (a *authService) addLdapBindDn(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }
@@ -391,7 +384,10 @@ func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
} }
// updateLdapBindDn updates a new LDAP via Bind DN authentication source. // updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error { func (a *authService) updateLdapBindDn(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }
@@ -410,11 +406,14 @@ func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) erro
} }
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error { func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }
@@ -436,7 +435,10 @@ func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) err
} }
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error { func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }

View File

@@ -8,16 +8,17 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestAddLdapBindDn(t *testing.T) { func TestAddLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@@ -134,7 +135,7 @@ func TestAddLdapBindDn(t *testing.T) {
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
"--email-attribute", "mail", "--email-attribute", "mail",
}, },
errMsg: "unknown security protocol name: zzzzz", errMsg: "Unknown security protocol name: zzzzz",
}, },
// case 3 // case 3
{ {
@@ -238,13 +239,12 @@ func TestAddLdapBindDn(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{ app := cli.NewApp()
Flags: microcmdAuthAddLdapBindDn().Flags, app.Flags = microcmdAuthAddLdapBindDn.Flags
Action: service.addLdapBindDn, app.Action = service.addLdapBindDn
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@@ -256,7 +256,9 @@ func TestAddLdapBindDn(t *testing.T) {
func TestAddLdapSimpleAuth(t *testing.T) { func TestAddLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@@ -346,12 +348,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
"--name", "ldap (simple auth) source", "--name", "ldap (simple auth) source",
"--security-protocol", "zzzzz", "--security-protocol", "zzzzz",
"--host", "ldap-server", "--host", "ldap-server",
"--port", "1234", "--port", "123",
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))", "--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
"--email-attribute", "mail", "--email-attribute", "mail",
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
}, },
errMsg: "unknown security protocol name: zzzzz", errMsg: "Unknown security protocol name: zzzzz",
}, },
// case 3 // case 3
{ {
@@ -468,13 +470,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := &cli.Command{ app := cli.NewApp()
Flags: microcmdAuthAddLdapSimpleAuth().Flags, app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
Action: service.addLdapSimpleAuth, app.Action = service.addLdapSimpleAuth
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@@ -486,7 +487,9 @@ func TestAddLdapSimpleAuth(t *testing.T) {
func TestUpdateLdapBindDn(t *testing.T) { func TestUpdateLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@@ -861,7 +864,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
"--id", "1", "--id", "1",
"--security-protocol", "xxxxx", "--security-protocol", "xxxxx",
}, },
errMsg: "unknown security protocol name: xxxxx", errMsg: "Unknown security protocol name: xxxxx",
}, },
// case 22 // case 22
{ {
@@ -880,7 +883,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
Type: auth.OAuth2, Type: auth.OAuth2,
Cfg: &ldap.Source{}, Cfg: &ldap.Source{},
}, },
errMsg: "invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
}, },
// case 24 // case 24
{ {
@@ -944,12 +947,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{ app := cli.NewApp()
Flags: microcmdAuthUpdateLdapBindDn().Flags, app.Flags = microcmdAuthUpdateLdapBindDn.Flags
Action: service.updateLdapBindDn, app.Action = service.updateLdapBindDn
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@@ -961,7 +964,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
func TestUpdateLdapSimpleAuth(t *testing.T) { func TestUpdateLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@@ -1252,7 +1257,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
"--id", "1", "--id", "1",
"--security-protocol", "xxxxx", "--security-protocol", "xxxxx",
}, },
errMsg: "unknown security protocol name: xxxxx", errMsg: "Unknown security protocol name: xxxxx",
}, },
// case 18 // case 18
{ {
@@ -1271,7 +1276,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
Type: auth.PAM, Type: auth.PAM,
Cfg: &ldap.Source{}, Cfg: &ldap.Source{},
}, },
errMsg: "invalid authentication type. expected: LDAP (simple auth), actual: PAM", errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM",
}, },
// case 20 // case 20
{ {
@@ -1332,12 +1337,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{ app := cli.NewApp()
Flags: microcmdAuthUpdateLdapSimpleAuth().Flags, app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
Action: service.updateLdapSimpleAuth, app.Action = service.updateLdapSimpleAuth
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
@@ -13,11 +12,11 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func oauthCLIFlags() []cli.Flag { var (
return []cli.Flag{ oauthCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
@@ -87,14 +86,6 @@ func oauthCLIFlags() []cli.Flag {
Value: nil, Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source", Usage: "Scopes to request when to authenticate against this OAuth2 source",
}, },
&cli.StringFlag{
Name: "ssh-public-key-claim-name",
Usage: "Claim name that provides SSH public keys",
},
&cli.StringFlag{
Name: "full-name-claim-name",
Usage: "Claim name that provides user's full name",
},
&cli.StringFlag{ &cli.StringFlag{
Name: "required-claim-name", Name: "required-claim-name",
Value: "", Value: "",
@@ -130,34 +121,23 @@ func oauthCLIFlags() []cli.Flag {
Usage: "Activate automatic team membership removal depending on groups", Usage: "Activate automatic team membership removal depending on groups",
}, },
} }
}
func microcmdAuthAddOauth() *cli.Command { microcmdAuthAddOauth = &cli.Command{
return &cli.Command{
Name: "add-oauth", Name: "add-oauth",
Usage: "Add new Oauth authentication source", Usage: "Add new Oauth authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: runAddOauth,
return newAuthService().runAddOauth(ctx, cmd) Flags: oauthCLIFlags,
},
Flags: oauthCLIFlags(),
} }
}
func microcmdAuthUpdateOauth() *cli.Command { microcmdAuthUpdateOauth = &cli.Command{
return &cli.Command{
Name: "update-oauth", Name: "update-oauth",
Usage: "Update existing Oauth authentication source", Usage: "Update existing Oauth authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: runUpdateOauth,
return newAuthService().runUpdateOauth(ctx, cmd) Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
},
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}}, oauthCLIFlags()[1:]...)...),
} }
} )
func parseOAuth2Config(c *cli.Command) *oauth2.Source { func parseOAuth2Config(c *cli.Context) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") { if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{ customURLMapping = &oauth2.CustomURLMapping{
@@ -185,13 +165,14 @@ func parseOAuth2Config(c *cli.Command) *oauth2.Source {
RestrictedGroup: c.String("restricted-group"), RestrictedGroup: c.String("restricted-group"),
GroupTeamMap: c.String("group-team-map"), GroupTeamMap: c.String("group-team-map"),
GroupTeamMapRemoval: c.Bool("group-team-map-removal"), GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
SSHPublicKeyClaimName: c.String("ssh-public-key-claim-name"),
FullNameClaimName: c.String("full-name-claim-name"),
} }
} }
func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error { func runAddOauth(c *cli.Context) error {
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
@@ -203,7 +184,7 @@ func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error {
} }
} }
return a.createAuthSource(ctx, &auth_model.Source{ return auth_model.CreateSource(ctx, &auth_model.Source{
Type: auth_model.OAuth2, Type: auth_model.OAuth2,
Name: c.String("name"), Name: c.String("name"),
IsActive: true, IsActive: true,
@@ -212,16 +193,19 @@ func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error {
}) })
} }
func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error { func runUpdateOauth(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
source, err := a.getAuthSourceByID(ctx, c.Int64("id")) source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
if err != nil { if err != nil {
return err return err
} }
@@ -278,12 +262,6 @@ func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error
if c.IsSet("group-team-map-removal") { if c.IsSet("group-team-map-removal") {
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal") oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
} }
if c.IsSet("ssh-public-key-claim-name") {
oAuth2Config.SSHPublicKeyClaimName = c.String("ssh-public-key-claim-name")
}
if c.IsSet("full-name-claim-name") {
oAuth2Config.FullNameClaimName = c.String("full-name-claim-name")
}
// update custom URL mapping // update custom URL mapping
customURLMapping := &oauth2.CustomURLMapping{} customURLMapping := &oauth2.CustomURLMapping{}
@@ -318,5 +296,5 @@ func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error
oAuth2Config.CustomURLMapping = customURLMapping oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config source.Cfg = oAuth2Config
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "") source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(ctx, source) return auth_model.UpdateSource(ctx, source)
} }

View File

@@ -1,343 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
)
func TestAddOauth(t *testing.T) {
testCases := []struct {
name string
args []string
source *auth_model.Source
errMsg string
}{
{
name: "valid config",
args: []string{
"--name", "test",
"--provider", "github",
"--key", "some_key",
"--secret", "some_secret",
},
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Scopes: []string{},
Provider: "github",
ClientID: "some_key",
ClientSecret: "some_secret",
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with openid connect",
args: []string{
"--name", "test",
"--provider", "openidConnect",
"--key", "some_key",
"--secret", "some_secret",
"--auto-discover-url", "https://example.com",
},
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Scopes: []string{},
Provider: "openidConnect",
ClientID: "some_key",
ClientSecret: "some_secret",
OpenIDConnectAutoDiscoveryURL: "https://example.com",
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with options",
args: []string{
"--name", "test",
"--provider", "gitlab",
"--key", "some_key",
"--secret", "some_secret",
"--use-custom-urls", "true",
"--custom-token-url", "https://example.com/token",
"--custom-auth-url", "https://example.com/auth",
"--custom-profile-url", "https://example.com/profile",
"--custom-email-url", "https://example.com/email",
"--custom-tenant-id", "some_tenant",
"--icon-url", "https://example.com/icon",
"--scopes", "scope1,scope2",
"--skip-local-2fa", "true",
"--required-claim-name", "claim_name",
"--required-claim-value", "claim_value",
"--group-claim-name", "group_name",
"--admin-group", "admin",
"--restricted-group", "restricted",
"--group-team-map", `{"group1": [1,2]}`,
"--group-team-map-removal=true",
"--ssh-public-key-claim-name", "attr_ssh_pub_key",
"--full-name-claim-name", "attr_full_name",
},
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "some_key",
ClientSecret: "some_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://example.com/token",
AuthURL: "https://example.com/auth",
ProfileURL: "https://example.com/profile",
EmailURL: "https://example.com/email",
Tenant: "some_tenant",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: true,
SSHPublicKeyClaimName: "attr_ssh_pub_key",
FullNameClaimName: "attr_full_name",
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var createdSource *auth_model.Source
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
createdSource = source
return nil
},
}
app := &cli.Command{
Flags: microcmdAuthAddOauth().Flags,
Action: a.runAddOauth,
}
args := []string{"oauth-test"}
args = append(args, tc.args...)
err := app.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.source, createdSource)
}
})
}
}
func TestUpdateOauth(t *testing.T) {
testCases := []struct {
name string
args []string
id int64
existingAuthSource *auth_model.Source
authSource *auth_model.Source
errMsg string
}{
{
name: "missing id",
args: []string{
"--name", "test",
},
errMsg: "--id flag is missing",
},
{
name: "valid config",
id: 1,
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "old name",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "github",
ClientID: "old_key",
ClientSecret: "old_secret",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
"--provider", "gitlab",
"--key", "new_key",
"--secret", "new_secret",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "new_key",
ClientSecret: "new_secret",
CustomURLMapping: &oauth2.CustomURLMapping{},
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with options",
id: 1,
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "old name",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "old_key",
ClientSecret: "old_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://old.example.com/token",
AuthURL: "https://old.example.com/auth",
ProfileURL: "https://old.example.com/profile",
EmailURL: "https://old.example.com/email",
Tenant: "old_tenant",
},
IconURL: "https://old.example.com/icon",
Scopes: []string{"old_scope1", "old_scope2"},
RequiredClaimName: "old_claim_name",
RequiredClaimValue: "old_claim_value",
GroupClaimName: "old_group_name",
AdminGroup: "old_admin",
RestrictedGroup: "old_restricted",
GroupTeamMap: `{"old_group1": [1,2]}`,
GroupTeamMapRemoval: true,
SSHPublicKeyClaimName: "old_ssh_pub_key",
FullNameClaimName: "old_full_name",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
"--provider", "github",
"--key", "new_key",
"--secret", "new_secret",
"--use-custom-urls", "true",
"--custom-token-url", "https://example.com/token",
"--custom-auth-url", "https://example.com/auth",
"--custom-profile-url", "https://example.com/profile",
"--custom-email-url", "https://example.com/email",
"--custom-tenant-id", "new_tenant",
"--icon-url", "https://example.com/icon",
"--scopes", "scope1,scope2",
"--skip-local-2fa=true",
"--required-claim-name", "claim_name",
"--required-claim-value", "claim_value",
"--group-claim-name", "group_name",
"--admin-group", "admin",
"--restricted-group", "restricted",
"--group-team-map", `{"group1": [1,2]}`,
"--group-team-map-removal=false",
"--ssh-public-key-claim-name", "new_ssh_pub_key",
"--full-name-claim-name", "new_full_name",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "github",
ClientID: "new_key",
ClientSecret: "new_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://example.com/token",
AuthURL: "https://example.com/auth",
ProfileURL: "https://example.com/profile",
EmailURL: "https://example.com/email",
Tenant: "new_tenant",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: false,
SSHPublicKeyClaimName: "new_ssh_pub_key",
FullNameClaimName: "new_full_name",
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
return &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
CustomURLMapping: &oauth2.CustomURLMapping{},
},
TwoFactorPolicy: "skip",
}, nil
},
updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
assert.Equal(t, tc.authSource, source)
return nil
},
}
app := &cli.Command{
Flags: microcmdAuthUpdateOauth().Flags,
Action: a.runUpdateOauth,
}
args := []string{"oauth-test"}
args = append(args, tc.args...)
err := app.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
}
})
}
}

View File

@@ -1,271 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/auth/source/smtp"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
)
func TestAddSMTP(t *testing.T) {
testCases := []struct {
name string
args []string
source *auth_model.Source
errMsg string
}{
{
name: "missing name",
args: []string{
"--host", "localhost",
"--port", "25",
},
errMsg: "name must be set",
},
{
name: "missing host",
args: []string{
"--name", "test",
"--port", "25",
},
errMsg: "host must be set",
},
{
name: "missing port",
args: []string{
"--name", "test",
"--host", "localhost",
},
errMsg: "port must be set",
},
{
name: "valid config",
args: []string{
"--name", "test",
"--host", "localhost",
"--port", "25",
},
source: &auth_model.Source{
Type: auth_model.SMTP,
Name: "test",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "localhost",
Port: 25,
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with options",
args: []string{
"--name", "test",
"--host", "localhost",
"--port", "25",
"--auth-type", "LOGIN",
"--force-smtps",
"--skip-verify",
"--helo-hostname", "example.com",
"--disable-helo=true",
"--allowed-domains", "example.com,example.org",
"--skip-local-2fa",
"--active=false",
},
source: &auth_model.Source{
Type: auth_model.SMTP,
Name: "test",
IsActive: false,
Cfg: &smtp.Source{
Auth: "LOGIN",
Host: "localhost",
Port: 25,
ForceSMTPS: true,
SkipVerify: true,
HeloHostname: "example.com",
DisableHelo: true,
AllowedDomains: "example.com,example.org",
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
assert.Equal(t, tc.source, source)
return nil
},
}
cmd := &cli.Command{
Flags: microcmdAuthAddSMTP().Flags,
Action: a.runAddSMTP,
}
args := []string{"smtp-test"}
args = append(args, tc.args...)
t.Log(args)
err := cmd.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
}
})
}
}
func TestUpdateSMTP(t *testing.T) {
testCases := []struct {
name string
args []string
existingAuthSource *auth_model.Source
authSource *auth_model.Source
errMsg string
}{
{
name: "missing id",
args: []string{
"--name", "test",
"--host", "localhost",
"--port", "25",
},
errMsg: "--id flag is missing",
},
{
name: "valid config",
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "old name",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "old host",
Port: 26,
},
},
args: []string{
"--id", "1",
"--name", "test",
"--host", "localhost",
"--port", "25",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "test",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "localhost",
Port: 25,
},
},
},
{
name: "valid config with options",
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "old name",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "old host",
Port: 26,
HeloHostname: "old.example.com",
AllowedDomains: "old.example.com",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
"--host", "localhost",
"--port", "25",
"--auth-type", "LOGIN",
"--force-smtps",
"--skip-verify",
"--helo-hostname", "example.com",
"--disable-helo",
"--allowed-domains", "example.com,example.org",
"--skip-local-2fa",
"--active=false",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "test",
IsActive: false,
Cfg: &smtp.Source{
Auth: "LOGIN",
Host: "localhost",
Port: 25,
ForceSMTPS: true,
SkipVerify: true,
HeloHostname: "example.com",
DisableHelo: true,
AllowedDomains: "example.com,example.org",
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
return &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "test",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
},
}, nil
},
updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
assert.Equal(t, tc.authSource, source)
return nil
},
}
app := &cli.Command{
Flags: microcmdAuthUpdateSMTP().Flags,
Action: a.runUpdateSMTP,
}
args := []string{"smtp-tests"}
args = append(args, tc.args...)
err := app.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
}
})
}
}

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"strings" "strings"
@@ -12,11 +11,11 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/smtp" "code.gitea.io/gitea/services/auth/source/smtp"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func smtpCLIFlags() []cli.Flag { var (
return []cli.Flag{ smtpCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
@@ -39,10 +38,12 @@ func smtpCLIFlags() []cli.Flag {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "force-smtps", Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.", Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
Value: true,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-verify", Name: "skip-verify",
Usage: "Skip TLS verify.", Usage: "Skip TLS verify.",
Value: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "helo-hostname", Name: "helo-hostname",
@@ -52,6 +53,7 @@ func smtpCLIFlags() []cli.Flag {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "disable-helo", Name: "disable-helo",
Usage: "Disable SMTP helo.", Usage: "Disable SMTP helo.",
Value: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "allowed-domains", Name: "allowed-domains",
@@ -61,6 +63,7 @@ func smtpCLIFlags() []cli.Flag {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-local-2fa", Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.", Usage: "Skip 2FA to log on.",
Value: true,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "active", Name: "active",
@@ -68,34 +71,23 @@ func smtpCLIFlags() []cli.Flag {
Value: true, Value: true,
}, },
} }
}
func microcmdAuthUpdateSMTP() *cli.Command { microcmdAuthAddSMTP = &cli.Command{
return &cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().runUpdateSMTP(ctx, cmd)
},
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}}, smtpCLIFlags()[1:]...)...),
}
}
func microcmdAuthAddSMTP() *cli.Command {
return &cli.Command{
Name: "add-smtp", Name: "add-smtp",
Usage: "Add new SMTP authentication source", Usage: "Add new SMTP authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: runAddSMTP,
return newAuthService().runAddSMTP(ctx, cmd) Flags: smtpCLIFlags,
},
Flags: smtpCLIFlags(),
} }
}
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { microcmdAuthUpdateSMTP = &cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP,
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
}
)
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
if c.IsSet("auth-type") { if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type") conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
@@ -128,8 +120,11 @@ func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
return nil return nil
} }
func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error { func runAddSMTP(c *cli.Context) error {
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
@@ -157,7 +152,7 @@ func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
smtpConfig.Auth = "PLAIN" smtpConfig.Auth = "PLAIN"
} }
return a.createAuthSource(ctx, &auth_model.Source{ return auth_model.CreateSource(ctx, &auth_model.Source{
Type: auth_model.SMTP, Type: auth_model.SMTP,
Name: c.String("name"), Name: c.String("name"),
IsActive: active, IsActive: active,
@@ -166,16 +161,19 @@ func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
}) })
} }
func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error { func runUpdateSMTP(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
source, err := a.getAuthSourceByID(ctx, c.Int64("id")) source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
if err != nil { if err != nil {
return err return err
} }
@@ -196,5 +194,5 @@ func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error {
source.Cfg = smtpConfig source.Cfg = smtpConfig
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "") source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(ctx, source) return auth_model.UpdateSource(ctx, source)
} }

View File

@@ -4,13 +4,11 @@
package cmd package cmd
import ( import (
"context"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@@ -27,14 +25,20 @@ var (
} }
) )
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { func runRegenerateHooks(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
} }
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error { func runRegenerateKeys(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View File

@@ -4,18 +4,18 @@
package cmd package cmd
import ( import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var subcmdUser = &cli.Command{ var subcmdUser = &cli.Command{
Name: "user", Name: "user",
Usage: "Modify users", Usage: "Modify users",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdUserCreate(), microcmdUserCreate,
microcmdUserList, microcmdUserList,
microcmdUserChangePassword(), microcmdUserChangePassword,
microcmdUserDelete(), microcmdUserDelete,
microcmdUserGenerateAccessToken, microcmdUserGenerateAccessToken,
microcmdUserMustChangePassword(), microcmdUserMustChangePassword,
}, },
} }

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
@@ -14,11 +13,10 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserChangePassword() *cli.Command { var microcmdUserChangePassword = &cli.Command{
return &cli.Command{
Name: "change-password", Name: "change-password",
Usage: "Change a user's password", Usage: "Change a user's password",
Action: runChangePassword, Action: runChangePassword,
@@ -26,14 +24,14 @@ func microcmdUserChangePassword() *cli.Command {
&cli.StringFlag{ &cli.StringFlag{
Name: "username", Name: "username",
Aliases: []string{"u"}, Aliases: []string{"u"},
Value: "",
Usage: "The user to change password for", Usage: "The user to change password for",
Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "password", Name: "password",
Aliases: []string{"p"}, Aliases: []string{"p"},
Value: "",
Usage: "New password to set for user", Usage: "New password to set for user",
Required: true,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "must-change-password", Name: "must-change-password",
@@ -41,14 +39,18 @@ func microcmdUserChangePassword() *cli.Command {
Value: true, Value: true,
}, },
}, },
}
} }
func runChangePassword(ctx context.Context, c *cli.Command) error { func runChangePassword(c *cli.Context) error {
if !setting.IsInTesting { if err := argsSet(c, "username", "password"); err != nil {
if err := initDB(ctx); err != nil {
return err return err
} }
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
} }
user, err := user_model.GetUserByName(ctx, c.String("username")) user, err := user_model.GetUserByName(ctx, c.String("username"))

View File

@@ -1,91 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestChangePasswordCommand(t *testing.T) {
ctx := t.Context()
defer func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
}()
t.Run("change password successfully", func(t *testing.T) {
// defer func() {
// require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
// }()
// Prepare test user
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(ctx, []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
// load test user
userBase := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
// Change the password
err = microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "newpassword"})
require.NoError(t, err)
// Verify the password has been changed
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.NotEqual(t, userBase.Passwd, user.Passwd)
assert.NotEqual(t, userBase.Salt, user.Salt)
// Additional check for must-change-password flag
require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "anotherpassword", "--must-change-password=false"}))
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, user.MustChangePassword)
require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "yetanotherpassword", "--must-change-password"}))
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, user.MustChangePassword)
})
t.Run("failure cases", func(t *testing.T) {
testCases := []struct {
name string
args []string
expectedErr string
}{
{
name: "user does not exist",
args: []string{"change-password", "--username", "nonexistentuser", "--password", "newpassword"},
expectedErr: "user does not exist",
},
{
name: "missing username",
args: []string{"change-password", "--password", "newpassword"},
expectedErr: `"username" not set`,
},
{
name: "missing password",
args: []string{"change-password", "--username", "testuser"},
expectedErr: `"password" not set`,
},
{
name: "too short password",
args: []string{"change-password", "--username", "testuser", "--password", "1"},
expectedErr: "password is not long enough",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := microcmdUserChangePassword().Run(ctx, tc.args)
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
})
}
})
}

View File

@@ -16,18 +16,14 @@ import (
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserCreate() *cli.Command { var microcmdUserCreate = &cli.Command{
return &cli.Command{
Name: "create", Name: "create",
Usage: "Create a new user in database", Usage: "Create a new user in database",
Action: runCreateUser, Action: runCreateUser,
MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{ Flags: []cli.Flag{
{
Flags: [][]cli.Flag{
{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "Username. DEPRECATED: use username instead", Usage: "Username. DEPRECATED: use username instead",
@@ -36,12 +32,6 @@ func microcmdUserCreate() *cli.Command {
Name: "username", Name: "username",
Usage: "Username", Usage: "Username",
}, },
},
},
Required: true,
},
},
Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "user-type", Name: "user-type",
Usage: "Set user's type: individual or bot", Usage: "Set user's type: individual or bot",
@@ -54,7 +44,6 @@ func microcmdUserCreate() *cli.Command {
&cli.StringFlag{ &cli.StringFlag{
Name: "email", Name: "email",
Usage: "User email address", Usage: "User email address",
Required: true,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "admin", Name: "admin",
@@ -67,7 +56,7 @@ func microcmdUserCreate() *cli.Command {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "must-change-password", Name: "must-change-password",
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)", Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
HideDefault: true, DisableDefaultText: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "random-password-length", Name: "random-password-length",
@@ -97,14 +86,17 @@ func microcmdUserCreate() *cli.Command {
Usage: `The full, human-readable name of the user`, Usage: `The full, human-readable name of the user`,
}, },
}, },
}
} }
func runCreateUser(ctx context.Context, c *cli.Command) error { func runCreateUser(c *cli.Context) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first // this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future. // duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings() setting.LoadSettings()
if err := argsSet(c, "email"); err != nil {
return err
}
userTypes := map[string]user_model.UserType{ userTypes := map[string]user_model.UserType{
"individual": user_model.UserTypeIndividual, "individual": user_model.UserTypeIndividual,
"bot": user_model.UserTypeBot, "bot": user_model.UserTypeBot,
@@ -121,6 +113,12 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
return errors.New("password can only be set for individual users") return errors.New("password can only be set for individual users")
} }
} }
if c.IsSet("name") && c.IsSet("username") {
return errors.New("cannot set both --name and --username flags")
}
if !c.IsSet("name") && !c.IsSet("username") {
return errors.New("one of --name or --username flags must be set")
}
if c.IsSet("password") && c.IsSet("random-password") { if c.IsSet("password") && c.IsSet("random-password") {
return errors.New("cannot set both -random-password and -password flags") return errors.New("cannot set both -random-password and -password flags")
@@ -131,12 +129,16 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
username = c.String("username") username = c.String("username")
} else { } else {
username = c.String("name") username = c.String("name")
_, _ = fmt.Fprintf(c.ErrWriter, "--name flag is deprecated. Use --username instead.\n") _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
} }
ctx := c.Context
if !setting.IsInTesting { if !setting.IsInTesting {
// FIXME: need to refactor the "initDB" related code later // FIXME: need to refactor the "installSignals/initDB" related code later
// it doesn't make sense to call it in (almost) every command action function // it doesn't make sense to call it in (almost) every command action function
var cancel context.CancelFunc
ctx, cancel = installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View File

@@ -18,10 +18,12 @@ import (
) )
func TestAdminUserCreate(t *testing.T) { func TestAdminUserCreate(t *testing.T) {
app := NewMainApp(AppVersion{})
reset := func() { reset := func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
} }
t.Run("MustChangePassword", func(t *testing.T) { t.Run("MustChangePassword", func(t *testing.T) {
@@ -29,9 +31,8 @@ func TestAdminUserCreate(t *testing.T) {
IsAdmin bool IsAdmin bool
MustChangePassword bool MustChangePassword bool
} }
createCheck := func(name, args string) check { createCheck := func(name, args string) check {
require.NoError(t, microcmdUserCreate().Run(t.Context(), strings.Fields(fmt.Sprintf("create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) require.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name}) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword} return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword}
} }
@@ -50,7 +51,7 @@ func TestAdminUserCreate(t *testing.T) {
}) })
createUser := func(name string, args ...string) error { createUser := func(name string, args ...string) error {
return microcmdUserCreate().Run(t.Context(), append([]string{"create", "--username", name, "--email", name + "@gitea.local"}, args...)) return app.Run(append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...))
} }
t.Run("UserType", func(t *testing.T) { t.Run("UserType", func(t *testing.T) {

View File

@@ -4,21 +4,18 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserDelete() *cli.Command { var microcmdUserDelete = &cli.Command{
return &cli.Command{
Name: "delete", Name: "delete",
Usage: "Delete specific user by id, name or email", Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{ Flags: []cli.Flag{
@@ -42,19 +39,19 @@ func microcmdUserDelete() *cli.Command {
}, },
}, },
Action: runDeleteUser, Action: runDeleteUser,
}
} }
func runDeleteUser(ctx context.Context, c *cli.Command) error { func runDeleteUser(c *cli.Context) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return errors.New("You must provide the id, username or email of a user to delete") return errors.New("You must provide the id, username or email of a user to delete")
} }
if !setting.IsInTesting { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
}
if err := storage.Init(); err != nil { if err := storage.Init(); err != nil {
return err return err
@@ -73,11 +70,11 @@ func runDeleteUser(ctx context.Context, c *cli.Command) error {
return err return err
} }
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) { if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
return fmt.Errorf("the user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username")) return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
} }
if c.IsSet("id") && user.ID != c.Int64("id") { if c.IsSet("id") && user.ID != c.Int64("id") {
return fmt.Errorf("the user %s does not match the provided id %d", user.Name, c.Int64("id")) return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
} }
return user_service.DeleteUser(ctx, user, c.Bool("purge")) return user_service.DeleteUser(ctx, user, c.Bool("purge"))

View File

@@ -1,111 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"strconv"
"strings"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/require"
)
func TestAdminUserDelete(t *testing.T) {
ctx := t.Context()
defer func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
}()
setupTestUser := func(t *testing.T) {
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
}
t.Run("delete user by id", func(t *testing.T) {
setupTestUser(t)
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by username", func(t *testing.T) {
setupTestUser(t)
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by email", func(t *testing.T) {
setupTestUser(t)
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by all 3 attributes", func(t *testing.T) {
setupTestUser(t)
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
}
func TestAdminUserDeleteFailure(t *testing.T) {
testCases := []struct {
name string
args []string
expectedErr string
}{
{
name: "no user to delete",
args: []string{"delete", "--username", "nonexistentuser"},
expectedErr: "user does not exist",
},
{
name: "user exists but provided username does not match",
args: []string{"delete", "--email", "testuser@gitea.local", "--username", "wrongusername"},
expectedErr: "the user testuser who has email testuser@gitea.local does not match the provided username wrongusername",
},
{
name: "user exists but provided id does not match",
args: []string{"delete", "--username", "testuser", "--id", "999"},
expectedErr: "the user testuser does not match the provided id 999",
},
{
name: "no required flags are provided",
args: []string{"delete"},
expectedErr: "You must provide the id, username or email of a user to delete",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()
if strings.Contains(tc.name, "user exists") {
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
}
err := microcmdUserDelete().Run(ctx, tc.args)
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
})
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
}
}

View File

@@ -4,14 +4,13 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var microcmdUserGenerateAccessToken = &cli.Command{ var microcmdUserGenerateAccessToken = &cli.Command{
@@ -42,11 +41,14 @@ var microcmdUserGenerateAccessToken = &cli.Command{
Action: runGenerateAccessToken, Action: runGenerateAccessToken,
} }
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") { if !c.IsSet("username") {
return errors.New("you must provide a username to generate a token for") return errors.New("you must provide a username to generate a token for")
} }
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View File

@@ -4,14 +4,13 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"text/tabwriter" "text/tabwriter"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var microcmdUserList = &cli.Command{ var microcmdUserList = &cli.Command{
@@ -26,7 +25,10 @@ var microcmdUserList = &cli.Command{
}, },
} }
func runListUsers(ctx context.Context, c *cli.Command) error { func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View File

@@ -4,18 +4,15 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserMustChangePassword() *cli.Command { var microcmdUserMustChangePassword = &cli.Command{
return &cli.Command{
Name: "must-change-password", Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users", Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword, Action: runMustChangePassword,
@@ -35,10 +32,12 @@ func microcmdUserMustChangePassword() *cli.Command {
Usage: "Instead of setting the must-change-password flag, unset it", Usage: "Instead of setting the must-change-password flag, unset it",
}, },
}, },
}
} }
func runMustChangePassword(ctx context.Context, c *cli.Command) error { func runMustChangePassword(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if c.NArg() == 0 && !c.IsSet("all") { if c.NArg() == 0 && !c.IsSet("all") {
return errors.New("either usernames or --all must be provided") return errors.New("either usernames or --all must be provided")
} }
@@ -47,11 +46,9 @@ func runMustChangePassword(ctx context.Context, c *cli.Command) error {
all := c.Bool("all") all := c.Bool("all")
exclude := c.StringSlice("exclude") exclude := c.StringSlice("exclude")
if !setting.IsInTesting {
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
}
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude) n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)
if err != nil { if err != nil {

View File

@@ -1,78 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMustChangePassword(t *testing.T) {
defer func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
}()
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"})
require.NoError(t, err)
// Reset password change flag
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"})
require.NoError(t, err)
testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Make all users change password
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.True(t, testUserExclude.MustChangePassword)
// Reset password change flag but exclude all tested users
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.True(t, testUserExclude.MustChangePassword)
// Reset password change flag by listing multiple users
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Exclude a user from all user
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Unset a flag for single user
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
}

View File

@@ -6,7 +6,6 @@
package cmd package cmd
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
@@ -14,7 +13,6 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt"
"log" "log"
"math/big" "math/big"
"net" "net"
@@ -22,12 +20,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// cmdCert represents the available cert sub-command. // CmdCert represents the available cert sub-command.
func cmdCert() *cli.Command { var CmdCert = &cli.Command{
return &cli.Command{
Name: "cert", Name: "cert",
Usage: "Generate self-signed certificate", Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server. Description: `Generate a self-signed X.509 certificate for a TLS server.
@@ -36,8 +33,8 @@ Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "host", Name: "host",
Value: "",
Usage: "Comma-separated hostnames and IPs to generate a certificate for", Usage: "Comma-separated hostnames and IPs to generate a certificate for",
Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "ecdsa-curve", Name: "ecdsa-curve",
@@ -63,18 +60,7 @@ Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Name: "ca", Name: "ca",
Usage: "whether this cert should be its own Certificate Authority", Usage: "whether this cert should be its own Certificate Authority",
}, },
&cli.StringFlag{
Name: "out",
Value: "cert.pem",
Usage: "Path to the file where there certificate will be saved",
}, },
&cli.StringFlag{
Name: "keyout",
Value: "key.pem",
Usage: "Path to the file where there certificate key will be saved",
},
},
}
} }
func publicKey(priv any) any { func publicKey(priv any) any {
@@ -103,7 +89,11 @@ func pemBlockForKey(priv any) *pem.Block {
} }
} }
func runCert(_ context.Context, c *cli.Command) error { func runCert(c *cli.Context) error {
if err := argsSet(c, "host"); err != nil {
return err
}
var priv any var priv any
var err error var err error
switch c.String("ecdsa-curve") { switch c.String("ecdsa-curve") {
@@ -118,17 +108,17 @@ func runCert(_ context.Context, c *cli.Command) error {
case "P521": case "P521":
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default: default:
err = fmt.Errorf("unrecognized elliptic curve: %q", c.String("ecdsa-curve")) log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
} }
if err != nil { if err != nil {
return fmt.Errorf("failed to generate private key: %w", err) log.Fatalf("Failed to generate private key: %v", err)
} }
var notBefore time.Time var notBefore time.Time
if startDate := c.String("start-date"); startDate != "" { if startDate := c.String("start-date"); startDate != "" {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate) notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse creation date %w", err) log.Fatalf("Failed to parse creation date: %v", err)
} }
} else { } else {
notBefore = time.Now() notBefore = time.Now()
@@ -139,7 +129,7 @@ func runCert(_ context.Context, c *cli.Command) error {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil { if err != nil {
return fmt.Errorf("failed to generate serial number: %w", err) log.Fatalf("Failed to generate serial number: %v", err)
} }
template := x509.Certificate{ template := x509.Certificate{
@@ -156,8 +146,8 @@ func runCert(_ context.Context, c *cli.Command) error {
BasicConstraintsValid: true, BasicConstraintsValid: true,
} }
hosts := strings.SplitSeq(c.String("host"), ",") hosts := strings.Split(c.String("host"), ",")
for h := range hosts { for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil { if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip) template.IPAddresses = append(template.IPAddresses, ip)
} else { } else {
@@ -172,35 +162,35 @@ func runCert(_ context.Context, c *cli.Command) error {
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil { if err != nil {
return fmt.Errorf("failed to create certificate: %w", err) log.Fatalf("Failed to create certificate: %v", err)
} }
certOut, err := os.Create(c.String("out")) certOut, err := os.Create("cert.pem")
if err != nil { if err != nil {
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err) log.Fatalf("Failed to open cert.pem for writing: %v", err)
} }
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil { if err != nil {
return fmt.Errorf("failed to encode certificate: %w", err) log.Fatalf("Failed to encode certificate: %v", err)
} }
err = certOut.Close() err = certOut.Close()
if err != nil { if err != nil {
return fmt.Errorf("failed to write cert: %w", err) log.Fatalf("Failed to write cert: %v", err)
} }
fmt.Fprintf(c.Writer, "Written cert to %s\n", c.String("out")) log.Println("Written cert.pem")
keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil { if err != nil {
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err) log.Fatalf("Failed to open key.pem for writing: %v", err)
} }
err = pem.Encode(keyOut, pemBlockForKey(priv)) err = pem.Encode(keyOut, pemBlockForKey(priv))
if err != nil { if err != nil {
return fmt.Errorf("failed to encode key: %w", err) log.Fatalf("Failed to encode key: %v", err)
} }
err = keyOut.Close() err = keyOut.Close()
if err != nil { if err != nil {
return fmt.Errorf("failed to write key: %w", err) log.Fatalf("Failed to write key: %v", err)
} }
fmt.Fprintf(c.Writer, "Written key to %s\n", c.String("keyout")) log.Println("Written key.pem")
return nil return nil
} }

View File

@@ -1,123 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCertCommand(t *testing.T) {
cases := []struct {
name string
args []string
}{
{
name: "RSA cert generation",
args: []string{
"cert-test",
"--host", "localhost",
"--rsa-bits", "2048",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
{
name: "ECDSA cert generation",
args: []string{
"cert-test",
"--host", "localhost",
"--ecdsa-curve", "P256",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
{
name: "mixed host, certificate authority",
args: []string{
"cert-test",
"--host", "localhost,127.0.0.1",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
app := cmdCert()
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem")
keyFile := filepath.Join(tempDir, "key.pem")
err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
require.NoError(t, err)
assert.FileExists(t, certFile)
assert.FileExists(t, keyFile)
})
}
}
func TestCertCommandFailures(t *testing.T) {
cases := []struct {
name string
args []string
errMsg string
}{
{
name: "Start Date Parsing failure",
args: []string{
"cert-test",
"--host", "localhost",
"--start-date", "invalid-date",
},
errMsg: "parsing time",
},
{
name: "Unknown curve",
args: []string{
"cert-test",
"--host", "localhost",
"--ecdsa-curve", "invalid-curve",
},
errMsg: "unrecognized elliptic curve",
},
{
name: "Key generation failure",
args: []string{
"cert-test",
"--host", "localhost",
"--rsa-bits", "invalid-bits",
},
},
{
name: "Missing parameters",
args: []string{
"cert-test",
},
errMsg: `"host" not set`,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
app := cmdCert()
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem")
keyFile := filepath.Join(tempDir, "key.pem")
err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
require.Error(t, err)
if c.errMsg != "" {
assert.ErrorContains(t, err, c.errMsg)
}
assert.NoFileExists(t, certFile)
assert.NoFileExists(t, keyFile)
})
}
}

View File

@@ -18,19 +18,20 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// argsSet checks that all the required arguments are set. args is a list of // argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context. // arguments that must be set in the passed Context.
func argsSet(c *cli.Command, args ...string) error { func argsSet(c *cli.Context, args ...string) error {
for _, a := range args { for _, a := range args {
if !c.IsSet(a) { if !c.IsSet(a) {
return errors.New(a + " is not set") return errors.New(a + " is not set")
} }
if c.Value(a) == nil { if util.IsEmptyString(c.String(a)) {
return errors.New(a + " is required") return errors.New(a + " is required")
} }
} }
@@ -108,7 +109,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
} }
func globalBool(c *cli.Command, name string) bool { func globalBool(c *cli.Context, name string) bool {
for _, ctx := range c.Lineage() { for _, ctx := range c.Lineage() {
if ctx.Bool(name) { if ctx.Bool(name) {
return true return true
@@ -119,14 +120,8 @@ func globalBool(c *cli.Command, name string) bool {
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout. // PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever. // Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) { func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
return func(ctx context.Context, c *cli.Command) (context.Context, error) { return func(c *cli.Context) error {
if setting.InstallLock {
// During config loading, there might also be logs (for example: deprecation warnings).
// It must make sure that console logger is set up before config is loaded.
log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it.")
return nil, errors.New("console logger must be setup before config is loaded")
}
level := defaultLevel level := defaultLevel
if globalBool(c, "quiet") { if globalBool(c, "quiet") {
level = log.FATAL level = log.FATAL
@@ -135,16 +130,6 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cl
level = log.TRACE level = log.TRACE
} }
log.SetConsoleLogger(log.DEFAULT, "console-default", level) log.SetConsoleLogger(log.DEFAULT, "console-default", level)
return ctx, nil return nil
} }
} }
func isValidDefaultSubCommand(cmd *cli.Command) (string, bool) {
// Dirty patch for urfave/cli's strange design.
// "./gitea bad-cmd" should not start the web server.
rootArgs := cmd.Root().Args().Slice()
if len(rootArgs) != 0 && rootArgs[0] != cmd.Name {
return rootArgs[0], false
}
return "", true
}

View File

@@ -1,38 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
)
func TestDefaultCommand(t *testing.T) {
test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) {
called := false
cmd := &cli.Command{
DefaultCommand: "test",
Commands: []*cli.Command{
{
Name: "test",
Action: func(ctx context.Context, command *cli.Command) error {
retName, retValid := isValidDefaultSubCommand(command)
assert.Equal(t, expectedRetName, retName)
assert.Equal(t, expectedRetValid, retValid)
called = true
return nil
},
},
},
}
assert.NoError(t, cmd.Run(t.Context(), args))
assert.True(t, called)
}
test(t, []string{"./gitea"}, "", true)
test(t, []string{"./gitea", "test"}, "", true)
test(t, []string{"./gitea", "other"}, "other", false)
}

View File

@@ -4,13 +4,11 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
cli_docs "github.com/urfave/cli-docs/v3" "github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
) )
// CmdDocs represents the available docs sub-command. // CmdDocs represents the available docs sub-command.
@@ -32,16 +30,16 @@ var CmdDocs = &cli.Command{
}, },
} }
func runDocs(_ context.Context, cmd *cli.Command) error { func runDocs(ctx *cli.Context) error {
docs, err := cli_docs.ToMarkdown(cmd.Root()) docs, err := ctx.App.ToMarkdown()
if cmd.Bool("man") { if ctx.Bool("man") {
docs, err = cli_docs.ToMan(cmd.Root()) docs, err = ctx.App.ToMan()
} }
if err != nil { if err != nil {
return err return err
} }
if !cmd.Bool("man") { if !ctx.Bool("man") {
// Clean up markdown. The following bug was fixed in v2, but is present in v1. // Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages) // It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040 // https://github.com/urfave/cli/issues/1040
@@ -53,8 +51,8 @@ func runDocs(_ context.Context, cmd *cli.Command) error {
} }
out := os.Stdout out := os.Stdout
if cmd.String("output") != "" { if ctx.String("output") != "" {
fi, err := os.Create(cmd.String("output")) fi, err := os.Create(ctx.String("output"))
if err != nil { if err != nil {
return err return err
} }

View File

@@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/doctor" "code.gitea.io/gitea/services/doctor"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
"xorm.io/xorm" "xorm.io/xorm"
) )
@@ -30,7 +30,7 @@ var CmdDoctor = &cli.Command{
Usage: "Diagnose and optionally fix problems, convert or re-create database tables", 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.", 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.",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
cmdDoctorCheck, cmdDoctorCheck,
cmdRecreateTable, cmdRecreateTable,
cmdDoctorConvert, cmdDoctorConvert,
@@ -93,13 +93,16 @@ You should back-up your database before doing this and ensure that your database
Action: runRecreateTable, Action: runRecreateTable,
} }
func runRecreateTable(ctx context.Context, cmd *cli.Command) error { func runRecreateTable(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
defer cancel()
// Redirect the default golog to here // Redirect the default golog to here
golog.SetFlags(0) golog.SetFlags(0)
golog.SetPrefix("") golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
debug := cmd.Bool("debug") debug := ctx.Bool("debug")
setting.MustInstalled() setting.MustInstalled()
setting.LoadDBSetting() setting.LoadDBSetting()
@@ -110,15 +113,15 @@ func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
} }
setting.Database.LogSQL = debug setting.Database.LogSQL = debug
if err := db.InitEngine(ctx); err != nil { if err := db.InitEngine(stdCtx); err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil return nil
} }
args := cmd.Args() args := ctx.Args()
names := make([]string, 0, cmd.NArg()) names := make([]string, 0, ctx.NArg())
for i := 0; i < cmd.NArg(); i++ { for i := 0; i < ctx.NArg(); i++ {
names = append(names, args.Get(i)) names = append(names, args.Get(i))
} }
@@ -128,7 +131,7 @@ func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
} }
recreateTables := migrate_base.RecreateTables(beans...) recreateTables := migrate_base.RecreateTables(beans...)
return db.InitEngineWithMigration(context.Background(), func(ctx context.Context, x *xorm.Engine) error { return db.InitEngineWithMigration(stdCtx, func(ctx context.Context, x *xorm.Engine) error {
if err := migrations.EnsureUpToDate(ctx, x); err != nil { if err := migrations.EnsureUpToDate(ctx, x); err != nil {
return err return err
} }
@@ -136,11 +139,11 @@ func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
}) })
} }
func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) { func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
// Silence the default loggers // Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
logFile := cmd.String("log-file") logFile := ctx.String("log-file")
switch logFile { switch logFile {
case "": case "":
return // if no doctor log-file is set, do not show any log from default logger return // if no doctor log-file is set, do not show any log from default logger
@@ -158,20 +161,23 @@ func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) {
} }
} }
func runDoctorCheck(ctx context.Context, cmd *cli.Command) error { func runDoctorCheck(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
defer cancel()
colorize := log.CanColorStdout colorize := log.CanColorStdout
if cmd.IsSet("color") { if ctx.IsSet("color") {
colorize = cmd.Bool("color") colorize = ctx.Bool("color")
} }
setupDoctorDefaultLogger(cmd, colorize) setupDoctorDefaultLogger(ctx, colorize)
// Finally redirect the default golang's log to here // Finally redirect the default golang's log to here
golog.SetFlags(0) golog.SetFlags(0)
golog.SetPrefix("") golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
if cmd.IsSet("list") { if ctx.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n")) _, _ = w.Write([]byte("Default\tName\tTitle\n"))
doctor.SortChecks(doctor.Checks) doctor.SortChecks(doctor.Checks)
@@ -189,12 +195,12 @@ func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
} }
var checks []*doctor.Check var checks []*doctor.Check
if cmd.Bool("all") { if ctx.Bool("all") {
checks = make([]*doctor.Check, len(doctor.Checks)) checks = make([]*doctor.Check, len(doctor.Checks))
copy(checks, doctor.Checks) copy(checks, doctor.Checks)
} else if cmd.IsSet("run") { } else if ctx.IsSet("run") {
addDefault := cmd.Bool("default") addDefault := ctx.Bool("default")
runNamesSet := container.SetOf(cmd.StringSlice("run")...) runNamesSet := container.SetOf(ctx.StringSlice("run")...)
for _, check := range doctor.Checks { for _, check := range doctor.Checks {
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) { if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check) checks = append(checks, check)
@@ -211,5 +217,5 @@ func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
} }
} }
} }
return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks) return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
} }

View File

@@ -4,14 +4,13 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// cmdDoctorConvert represents the available convert sub-command. // cmdDoctorConvert represents the available convert sub-command.
@@ -22,8 +21,11 @@ var cmdDoctorConvert = &cli.Command{
Action: runDoctorConvert, Action: runDoctorConvert,
} }
func runDoctorConvert(ctx context.Context, cmd *cli.Command) error { func runDoctorConvert(ctx *cli.Context) error {
if err := initDB(ctx); err != nil { stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }

View File

@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/services/doctor" "code.gitea.io/gitea/services/doctor"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestDoctorRun(t *testing.T) { func TestDoctorRun(t *testing.T) {
@@ -22,13 +22,12 @@ func TestDoctorRun(t *testing.T) {
SkipDatabaseInitialization: true, SkipDatabaseInitialization: true,
}) })
app := &cli.Command{ app := cli.NewApp()
Commands: []*cli.Command{cmdDoctorCheck}, app.Commands = []*cli.Command{cmdDoctorCheck}
} err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
assert.NoError(t, err) assert.NoError(t, err)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"}) err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`) assert.ErrorContains(t, err, `unknown checks: "no-such"`)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"}) err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`) assert.ErrorContains(t, err, `unknown checks: "no-such"`)
} }

View File

@@ -5,7 +5,6 @@
package cmd package cmd
import ( import (
"context"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@@ -20,7 +19,8 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/go-chi/session" "gitea.com/go-chi/session"
"github.com/urfave/cli/v3" "github.com/mholt/archiver/v3"
"github.com/urfave/cli/v2"
) )
// CmdDump represents the available dump sub-command. // CmdDump represents the available dump sub-command.
@@ -101,17 +101,17 @@ func fatal(format string, args ...any) {
log.Fatal(format, args...) log.Fatal(format, args...)
} }
func runDump(ctx context.Context, cmd *cli.Command) error { func runDump(ctx *cli.Context) error {
setting.MustInstalled() setting.MustInstalled()
quite := cmd.Bool("quiet") quite := ctx.Bool("quiet")
verbose := cmd.Bool("verbose") verbose := ctx.Bool("verbose")
if verbose && quite { if verbose && quite {
fatal("Option --quiet and --verbose cannot both be set") fatal("Option --quiet and --verbose cannot both be set")
} }
// outFileName is either "-" or a file name (will be made absolute) // outFileName is either "-" or a file name (will be made absolute)
outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type")) outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type"))
if outType == "" { if outType == "" {
fatal("Invalid output type") fatal("Invalid output type")
} }
@@ -136,7 +136,10 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
setting.DisableLoggerInit() setting.DisableLoggerInit()
setting.LoadSettings() // cannot access session settings otherwise setting.LoadSettings() // cannot access session settings otherwise
err := db.InitEngine(ctx) stdCtx, cancel := installSignals()
defer cancel()
err := db.InitEngine(stdCtx)
if err != nil { if err != nil {
return err return err
} }
@@ -145,20 +148,24 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
return err return err
} }
dumper, err := dump.NewDumper(ctx, outType, outFile) archiverGeneric, err := archiver.ByExtension("." + outType)
if err != nil { if err != nil {
fatal("Failed to create archive %q: %v", outFile, err) fatal("Unable to get archiver for extension: %v", err)
return err
} }
dumper.Verbose = verbose
dumper.GlobalExcludeAbsPath(outFileName)
defer func() {
if err := dumper.Close(); err != nil {
fatal("Failed to save archive %q: %v", outFileName, err)
}
}()
if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") { archiverWriter := archiverGeneric.(archiver.Writer)
if err := archiverWriter.Create(outFile); err != nil {
fatal("Creating archiver.Writer failed: %v", err)
}
defer archiverWriter.Close()
dumper := &dump.Dumper{
Writer: archiverWriter,
Verbose: verbose,
}
dumper.GlobalExcludeAbsPath(outFileName)
if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
log.Info("Skip dumping local repositories") log.Info("Skip dumping local repositories")
} else { } else {
log.Info("Dumping local repositories... %s", setting.RepoRootPath) log.Info("Dumping local repositories... %s", setting.RepoRootPath)
@@ -166,7 +173,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
fatal("Failed to include repositories: %v", err) fatal("Failed to include repositories: %v", err)
} }
if cmd.IsSet("skip-lfs-data") && cmd.Bool("skip-lfs-data") { if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
log.Info("Skip dumping LFS data") log.Info("Skip dumping LFS data")
} else if !setting.LFS.StartServer { } else if !setting.LFS.StartServer {
log.Info("LFS isn't enabled. Skip dumping LFS data") log.Info("LFS isn't enabled. Skip dumping LFS data")
@@ -175,18 +182,18 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddFileByReader(object, info, path.Join("data", "lfs", objPath)) return dumper.AddReader(object, info, path.Join("data", "lfs", objPath))
}); err != nil { }); err != nil {
fatal("Failed to dump LFS objects: %v", err) fatal("Failed to dump LFS objects: %v", err)
} }
} }
if cmd.Bool("skip-db") { if ctx.Bool("skip-db") {
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere. // Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
dumper.GlobalExcludeAbsPath(setting.Database.Path) dumper.GlobalExcludeAbsPath(setting.Database.Path)
log.Info("Skipping database") log.Info("Skipping database")
} else { } else {
tmpDir := cmd.String("tempdir") tmpDir := ctx.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) { if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
fatal("Path does not exist: %s", tmpDir) fatal("Path does not exist: %s", tmpDir)
} }
@@ -202,7 +209,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
} }
}() }()
targetDBType := cmd.String("database") targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else { } else {
@@ -213,17 +220,17 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
fatal("Failed to dump database: %v", err) fatal("Failed to dump database: %v", err)
} }
if err = dumper.AddFileByPath("gitea-db.sql", dbDump.Name()); err != nil { if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil {
fatal("Failed to include gitea-db.sql: %v", err) fatal("Failed to include gitea-db.sql: %v", err)
} }
} }
log.Info("Adding custom configuration file from %s", setting.CustomConf) log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err = dumper.AddFileByPath("app.ini", setting.CustomConf); err != nil { if err = dumper.AddFile("app.ini", setting.CustomConf); err != nil {
fatal("Failed to include specified app.ini: %v", err) fatal("Failed to include specified app.ini: %v", err)
} }
if cmd.IsSet("skip-custom-dir") && cmd.Bool("skip-custom-dir") { if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
log.Info("Skipping custom directory") log.Info("Skipping custom directory")
} else { } else {
customDir, err := os.Stat(setting.CustomPath) customDir, err := os.Stat(setting.CustomPath)
@@ -256,7 +263,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
excludes = append(excludes, opts.ProviderConfig) excludes = append(excludes, opts.ProviderConfig)
} }
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") { if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
excludes = append(excludes, setting.Indexer.RepoPath) excludes = append(excludes, setting.Indexer.RepoPath)
excludes = append(excludes, setting.Indexer.IssuePath) excludes = append(excludes, setting.Indexer.IssuePath)
} }
@@ -265,26 +272,25 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
excludes = append(excludes, setting.LFS.Storage.Path) excludes = append(excludes, setting.LFS.Storage.Path)
excludes = append(excludes, setting.Attachment.Storage.Path) excludes = append(excludes, setting.Attachment.Storage.Path)
excludes = append(excludes, setting.Packages.Storage.Path) excludes = append(excludes, setting.Packages.Storage.Path)
excludes = append(excludes, setting.RepoArchive.Storage.Path)
excludes = append(excludes, setting.Log.RootPath) excludes = append(excludes, setting.Log.RootPath)
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil { if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
fatal("Failed to include data directory: %v", err) fatal("Failed to include data directory: %v", err)
} }
} }
if cmd.IsSet("skip-attachment-data") && cmd.Bool("skip-attachment-data") { if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
log.Info("Skip dumping attachment data") log.Info("Skip dumping attachment data")
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat() info, err := object.Stat()
if err != nil { if err != nil {
return err return err
} }
return dumper.AddFileByReader(object, info, path.Join("data", "attachments", objPath)) return dumper.AddReader(object, info, path.Join("data", "attachments", objPath))
}); err != nil { }); err != nil {
fatal("Failed to dump attachments: %v", err) fatal("Failed to dump attachments: %v", err)
} }
if cmd.IsSet("skip-package-data") && cmd.Bool("skip-package-data") { if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
log.Info("Skip dumping package data") log.Info("Skip dumping package data")
} else if !setting.Packages.Enabled { } else if !setting.Packages.Enabled {
log.Info("Packages isn't enabled. Skip dumping package data") log.Info("Packages isn't enabled. Skip dumping package data")
@@ -293,7 +299,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddFileByReader(object, info, path.Join("data", "packages", objPath)) return dumper.AddReader(object, info, path.Join("data", "packages", objPath))
}); err != nil { }); err != nil {
fatal("Failed to dump packages: %v", err) fatal("Failed to dump packages: %v", err)
} }
@@ -301,7 +307,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
// Doesn't check if LogRootPath exists before processing --skip-log intentionally, // Doesn't check if LogRootPath exists before processing --skip-log intentionally,
// ensuring that it's clear the dump is skipped whether the directory's initialized // ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not. // yet or not.
if cmd.IsSet("skip-log") && cmd.Bool("skip-log") { if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
log.Info("Skip dumping log files") log.Info("Skip dumping log files")
} else { } else {
isExist, err := util.IsExist(setting.Log.RootPath) isExist, err := util.IsExist(setting.Log.RootPath)
@@ -318,6 +324,10 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if outFileName == "-" { if outFileName == "-" {
log.Info("Finish dumping to stdout") log.Info("Finish dumping to stdout")
} else { } else {
if err = archiverWriter.Close(); err != nil {
_ = os.Remove(outFileName)
fatal("Failed to save %q: %v", outFileName, err)
}
if err = os.Chmod(outFileName, 0o600); err != nil { if err = os.Chmod(outFileName, 0o600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err) log.Info("Can't change file access permissions mask to 0600: %v", err)
} }

View File

@@ -19,7 +19,7 @@ import (
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdDumpRepository represents the available dump repository sub-command. // CmdDumpRepository represents the available dump repository sub-command.
@@ -79,18 +79,16 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
}, },
} }
func runDumpRepository(ctx context.Context, cmd *cli.Command) error { func runDumpRepository(ctx *cli.Context) error {
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr) stdCtx, cancel := installSignals()
defer cancel()
setting.DisableLoggerInit() if err := initDB(stdCtx); err != nil {
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
if err := initDB(ctx); err != nil {
return err return err
} }
// migrations.GiteaLocalUploader depends on git module // migrations.GiteaLocalUploader depends on git module
if err := git.InitSimple(); err != nil { if err := git.InitSimple(context.Background()); err != nil {
return err return err
} }
@@ -102,8 +100,8 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
var ( var (
serviceType structs.GitServiceType serviceType structs.GitServiceType
cloneAddr = cmd.String("clone_addr") cloneAddr = ctx.String("clone_addr")
serviceStr = cmd.String("git_service") serviceStr = ctx.String("git_service")
) )
if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") { if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
@@ -121,13 +119,13 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
opts := base.MigrateOptions{ opts := base.MigrateOptions{
GitServiceType: serviceType, GitServiceType: serviceType,
CloneAddr: cloneAddr, CloneAddr: cloneAddr,
AuthUsername: cmd.String("auth_username"), AuthUsername: ctx.String("auth_username"),
AuthPassword: cmd.String("auth_password"), AuthPassword: ctx.String("auth_password"),
AuthToken: cmd.String("auth_token"), AuthToken: ctx.String("auth_token"),
RepoName: cmd.String("repo_name"), RepoName: ctx.String("repo_name"),
} }
if len(cmd.String("units")) == 0 { if len(ctx.String("units")) == 0 {
opts.Wiki = true opts.Wiki = true
opts.Issues = true opts.Issues = true
opts.Milestones = true opts.Milestones = true
@@ -137,8 +135,8 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
opts.PullRequests = true opts.PullRequests = true
opts.ReleaseAssets = true opts.ReleaseAssets = true
} else { } else {
units := strings.SplitSeq(cmd.String("units"), ",") units := strings.Split(ctx.String("units"), ",")
for unit := range units { for _, unit := range units {
switch strings.ToLower(strings.TrimSpace(unit)) { switch strings.ToLower(strings.TrimSpace(unit)) {
case "": case "":
continue continue
@@ -166,7 +164,7 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
// the repo_dir will be removed if error occurs in DumpRepository // the repo_dir will be removed if error occurs in DumpRepository
// make sure the directory doesn't exist or is empty, prevent from deleting user files // make sure the directory doesn't exist or is empty, prevent from deleting user files
repoDir := cmd.String("repo_dir") repoDir := ctx.String("repo_dir")
if exists, err := util.IsExist(repoDir); err != nil { if exists, err := util.IsExist(repoDir); err != nil {
return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err) return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
} else if exists { } else if exists {
@@ -179,9 +177,9 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
} }
if err := migrations.DumpRepository( if err := migrations.DumpRepository(
ctx, context.Background(),
repoDir, repoDir,
cmd.String("owner_name"), ctx.String("owner_name"),
opts, opts,
); err != nil { ); err != nil {
log.Fatal("Failed to dump repository: %v", err) log.Fatal("Failed to dump repository: %v", err)

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -12,7 +11,6 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/assetfs" "code.gitea.io/gitea/modules/assetfs"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/public"
@@ -20,7 +18,8 @@ import (
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v3" "github.com/gobwas/glob"
"github.com/urfave/cli/v2"
) )
// CmdEmbedded represents the available extract sub-command. // CmdEmbedded represents the available extract sub-command.
@@ -29,7 +28,7 @@ var (
Name: "embedded", Name: "embedded",
Usage: "Extract embedded resources", Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images", Description: "A command for extracting embedded resources, like templates and images",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdList, subcmdList,
subcmdView, subcmdView,
subcmdExtract, subcmdExtract,
@@ -101,7 +100,7 @@ type assetFile struct {
path string path string
} }
func initEmbeddedExtractor(c *cli.Command) error { func initEmbeddedExtractor(c *cli.Context) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args().Slice()) patterns, err := compileCollectPatterns(c.Args().Slice())
@@ -116,31 +115,31 @@ func initEmbeddedExtractor(c *cli.Command) error {
return nil return nil
} }
func runList(_ context.Context, c *cli.Command) error { func runList(c *cli.Context) error {
if err := runListDo(c); err != nil { if err := runListDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runView(_ context.Context, c *cli.Command) error { func runView(c *cli.Context) error {
if err := runViewDo(c); err != nil { if err := runViewDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runExtract(_ context.Context, c *cli.Command) error { func runExtract(c *cli.Context) error {
if err := runExtractDo(c); err != nil { if err := runExtractDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runListDo(c *cli.Command) error { func runListDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@@ -152,7 +151,7 @@ func runListDo(c *cli.Command) error {
return nil return nil
} }
func runViewDo(c *cli.Command) error { func runViewDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@@ -175,7 +174,7 @@ func runViewDo(c *cli.Command) error {
return nil return nil
} }
func runExtractDo(c *cli.Command) error { func runExtractDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@@ -217,7 +216,7 @@ func runExtractDo(c *cli.Command) error {
for _, a := range matchedAssetFiles { for _, a := range matchedAssetFiles {
if err := extractAsset(destdir, a, overwrite, rename); err != nil { if err := extractAsset(destdir, a, overwrite, rename); err != nil {
// Non-fatal error // Non-fatal error
_, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", a.path, err) fmt.Fprintf(os.Stderr, "%s: %v", a.path, err)
} }
} }
@@ -272,7 +271,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
return nil return nil
} }
func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) { func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
fs := assetfs.Layered(layer) fs := assetfs.Layered(layer)
files, err := fs.ListAllFiles(".", true) files, err := fs.ListAllFiles(".", true)
if err != nil { if err != nil {
@@ -295,14 +294,16 @@ func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string,
} }
} }
func compileCollectPatterns(args []string) (_ []glob.Glob, err error) { func compileCollectPatterns(args []string) ([]glob.Glob, error) {
if len(args) == 0 { if len(args) == 0 {
args = []string{"**"} args = []string{"**"}
} }
pat := make([]glob.Glob, len(args)) pat := make([]glob.Glob, len(args))
for i := range args { for i := range args {
if pat[i], err = glob.Compile(args[i], '/'); err != nil { if g, err := glob.Compile(args[i], '/'); err != nil {
return nil, fmt.Errorf("invalid glob patterh %q: %w", args[i], err) return nil, fmt.Errorf("'%s': Invalid glob pattern: %w", args[i], err)
} else { //nolint:revive
pat[i] = g
} }
} }
return pat, nil return pat, nil

View File

@@ -5,14 +5,13 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@@ -20,7 +19,7 @@ var (
CmdGenerate = &cli.Command{ CmdGenerate = &cli.Command{
Name: "generate", Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens", Usage: "Generate Gitea's secrets/keys/tokens",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdSecret, subcmdSecret,
}, },
} }
@@ -28,7 +27,7 @@ var (
subcmdSecret = &cli.Command{ subcmdSecret = &cli.Command{
Name: "secret", Name: "secret",
Usage: "Generate a secret token", Usage: "Generate a secret token",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdGenerateInternalToken, microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret, microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey, microcmdGenerateSecretKey,
@@ -55,7 +54,7 @@ var (
} }
) )
func runGenerateInternalToken(_ context.Context, c *cli.Command) error { func runGenerateInternalToken(c *cli.Context) error {
internalToken, err := generate.NewInternalToken() internalToken, err := generate.NewInternalToken()
if err != nil { if err != nil {
return err return err
@@ -70,7 +69,7 @@ func runGenerateInternalToken(_ context.Context, c *cli.Command) error {
return nil return nil
} }
func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error { func runGenerateLfsJwtSecret(c *cli.Context) error {
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
if err != nil { if err != nil {
return err return err
@@ -85,7 +84,7 @@ func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error {
return nil return nil
} }
func runGenerateSecretKey(_ context.Context, c *cli.Command) error { func runGenerateSecretKey(c *cli.Context) error {
secretKey, err := generate.NewSecretKey() secretKey, err := generate.NewSecretKey()
if err != nil { if err != nil {
return err return err

View File

@@ -15,17 +15,16 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
const ( const (
hookBatchSize = 500 hookBatchSize = 30
) )
var ( var (
@@ -33,10 +32,9 @@ var (
CmdHook = &cli.Command{ CmdHook = &cli.Command{
Name: "hook", Name: "hook",
Usage: "(internal) Should only be called by Git", Usage: "(internal) Should only be called by Git",
Hidden: true, // internal commands shouldn't be visible
Description: "Delegate commands to corresponding Git hooks", Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdHookPreReceive, subcmdHookPreReceive,
subcmdHookUpdate, subcmdHookUpdate,
subcmdHookPostReceive, subcmdHookPostReceive,
@@ -163,10 +161,12 @@ func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil return len(s), nil
} }
func runHookPreReceive(ctx context.Context, c *cli.Command) error { func runHookPreReceive(c *cli.Context) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil return nil
} }
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
@@ -292,7 +292,7 @@ Gitea or set your environment appropriately.`, "")
// runHookUpdate avoid to do heavy operations on update hook because it will be // runHookUpdate avoid to do heavy operations on update hook because it will be
// invoked for every ref update which does not like pre-receive and post-receive // invoked for every ref update which does not like pre-receive and post-receive
func runHookUpdate(_ context.Context, c *cli.Command) error { func runHookUpdate(c *cli.Context) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil return nil
} }
@@ -309,12 +309,15 @@ func runHookUpdate(_ context.Context, c *cli.Command) error {
return nil return nil
} }
func runHookPostReceive(ctx context.Context, c *cli.Command) error { func runHookPostReceive(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what // First of all run update-server-info no matter what
if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil {
return fmt.Errorf("failed to call 'git update-server-info': %w", err) return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
} }
// Now if we're an internal don't do anything else // Now if we're an internal don't do anything else
@@ -482,7 +485,7 @@ func hookPrintResult(output, isCreate bool, branch, url string) {
func pushOptions() map[string]string { func pushOptions() map[string]string {
opts := make(map[string]string) opts := make(map[string]string)
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil { if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
for idx := range pushCount { for idx := 0; idx < pushCount; idx++ {
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx)) opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
kv := strings.SplitN(opt, "=", 2) kv := strings.SplitN(opt, "=", 2)
if len(kv) == 2 { if len(kv) == 2 {
@@ -493,7 +496,10 @@ func pushOptions() map[string]string {
return opts return opts
} }
func runHookProcReceive(ctx context.Context, c *cli.Command) error { func runHookProcReceive(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
@@ -734,7 +740,7 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType)
// read prefix // read prefix
lengthBytes := make([]byte, 4) lengthBytes := make([]byte, 4)
for i := range 4 { for i := 0; i < 4; i++ {
lengthBytes[i], err = in.ReadByte() lengthBytes[i], err = in.ReadByte()
if err != nil { if err != nil {
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err) return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@@ -12,14 +11,13 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdKeys represents the available keys sub-command // CmdKeys represents the available keys sub-command
var CmdKeys = &cli.Command{ var CmdKeys = &cli.Command{
Name: "keys", Name: "keys",
Usage: "(internal) Should only be called by SSH server", Usage: "(internal) Should only be called by SSH server",
Hidden: true, // internal commands shouldn't be visible
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint", Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runKeys, Action: runKeys,
@@ -51,7 +49,7 @@ var CmdKeys = &cli.Command{
}, },
} }
func runKeys(ctx context.Context, c *cli.Command) error { func runKeys(c *cli.Context) error {
if !c.IsSet("username") { if !c.IsSet("username") {
return errors.New("No username provided") return errors.New("No username provided")
} }
@@ -70,6 +68,9 @@ func runKeys(ctx context.Context, c *cli.Command) error {
return errors.New("No key type and content provided") return errors.New("No key type and content provided")
} }
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content) authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
@@ -77,6 +78,6 @@ func runKeys(ctx context.Context, c *cli.Command) error {
if extra.Error != nil { if extra.Error != nil {
return extra.Error return extra.Error
} }
_, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text)) _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
return nil return nil
} }

View File

@@ -4,18 +4,24 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func runSendMail(ctx context.Context, c *cli.Command) error { func runSendMail(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setting.MustInstalled() setting.MustInstalled()
if err := argsSet(c, "title"); err != nil {
return err
}
subject := c.String("title") subject := c.String("title")
confirmSkiped := c.Bool("force") confirmSkiped := c.Bool("force")
body := c.String("content") body := c.String("content")

View File

@@ -4,40 +4,36 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"io"
"os" "os"
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var cliHelpPrinterOld = cli.HelpPrinter // 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 init() { func cmdHelp() *cli.Command {
cli.HelpPrinter = cliHelpPrinterNew c := &cli.Command{
} Name: "help",
Aliases: []string{"h"},
// cliHelpPrinterNew helps to print "DEFAULT CONFIGURATION" for the following cases ( "-c" can apper in any position): Usage: "Shows a list of commands or help for one command",
// * ./gitea -c /dev/null -h ArgsUsage: "[command]",
// * ./gitea -c help /dev/null help Action: func(c *cli.Context) (err error) {
// * ./gitea help -c /dev/null lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
// * ./gitea help -c /dev/null web targetCmdIdx := 0
// * ./gitea help web -c /dev/null if c.Command.Name == "help" {
// * ./gitea web help -c /dev/null targetCmdIdx = 1
// * ./gitea web -h -c /dev/null
func cliHelpPrinterNew(out io.Writer, templ string, data any) {
cmd, _ := data.(*cli.Command)
if cmd != nil {
prepareWorkPathAndCustomConf(cmd)
} }
cliHelpPrinterOld(out, templ, data) if lineage[targetCmdIdx+1].Command != nil {
if setting.CustomConf != "" { err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
_, _ = fmt.Fprintf(out, ` } else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
DEFAULT CONFIGURATION: DEFAULT CONFIGURATION:
AppPath: %s AppPath: %s
WorkPath: %s WorkPath: %s
@@ -45,38 +41,75 @@ DEFAULT CONFIGURATION:
ConfigFile: %s ConfigFile: %s
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf) `, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
return err
},
}
return c
}
func appGlobalFlags() []cli.Flag {
return []cli.Flag{
// make the builtin flags at the top
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
// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
&cli.StringFlag{
Name: "custom-path",
Aliases: []string{"C"},
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
&cli.StringFlag{
Name: "work-path",
Aliases: []string{"w"},
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
} }
} }
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) { func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
originBefore := originCmd.Before command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) { command.Action = prepareWorkPathAndCustomConf(command.Action)
ctx = ctxOrig command.HideHelp = true
if originBefore != nil { if command.Name != "help" {
ctx, err = originBefore(ctx, cmd) command.Subcommands = append(command.Subcommands, cmdHelp())
if err != nil {
return ctx, err
} }
} for i := range command.Subcommands {
prepareWorkPathAndCustomConf(cmd) prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
return ctx, nil
} }
} }
// prepareWorkPathAndCustomConf tries to prepare the work path, custom path and custom config from various inputs: // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// command line flags, environment variables, config file // It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(cmd *cli.Command) { func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
var args setting.ArgWorkPathAndCustomConf var args setting.ArgWorkPathAndCustomConf
if cmd.IsSet("work-path") { // from children to parent, check the global flags
args.WorkPath = cmd.String("work-path") for _, curCtx := range ctx.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
} }
if cmd.IsSet("custom-path") { if curCtx.IsSet("custom-path") && args.CustomPath == "" {
args.CustomPath = cmd.String("custom-path") args.CustomPath = curCtx.String("custom-path")
}
if curCtx.IsSet("config") && args.CustomConf == "" {
args.CustomConf = curCtx.String("config")
} }
if cmd.IsSet("config") {
args.CustomConf = cmd.String("config")
} }
setting.InitWorkPathAndCommonConfig(os.Getenv, args) setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if ctx.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx)
}
return action(ctx)
}
} }
type AppVersion struct { type AppVersion struct {
@@ -84,36 +117,18 @@ type AppVersion struct {
Extra string Extra string
} }
func NewMainApp(appVer AppVersion) *cli.Command { func NewMainApp(appVer AppVersion) *cli.App {
app := &cli.Command{} app := cli.NewApp()
app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]" app.Name = "Gitea"
app.HelpName = "gitea"
app.Usage = "A painless self-hosted Git service" app.Usage = "A painless self-hosted Git service"
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.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 = appVer.Version + appVer.Extra app.Version = appVer.Version + appVer.Extra
app.EnableShellCompletion = true app.EnableBashCompletion = true
app.Flags = []cli.Flag{
&cli.StringFlag{ // these sub-commands need to use config file
Name: "work-path",
Aliases: []string{"w"},
TakesFile: true,
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
TakesFile: true,
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
&cli.StringFlag{
Name: "custom-path",
Aliases: []string{"C"},
TakesFile: true,
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
}
// these sub-commands need to use a config file
subCmdWithConfig := []*cli.Command{ subCmdWithConfig := []*cli.Command{
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
CmdWeb, CmdWeb,
CmdServ, CmdServ,
CmdHook, CmdHook,
@@ -132,18 +147,20 @@ func NewMainApp(appVer AppVersion) *cli.Command {
// these sub-commands do not need the config file, and they do not depend on any path or environment variable. // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{ subCmdStandalone := []*cli.Command{
cmdCert(), CmdCert,
CmdGenerate, CmdGenerate,
CmdDocs, CmdDocs,
} }
// TODO: we should eventually drop the default command,
// but not sure whether it would break Windows users who used to double-click the EXE to run.
app.DefaultCommand = CmdWeb.Name app.DefaultCommand = CmdWeb.Name
globalFlags := appGlobalFlags()
app.Flags = append(app.Flags, cli.VersionFlag)
app.Flags = append(app.Flags, globalFlags...)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO) app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig { for i := range subCmdWithConfig {
prepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
} }
app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...) app.Commands = append(app.Commands, subCmdStandalone...)
@@ -152,10 +169,8 @@ func NewMainApp(appVer AppVersion) *cli.Command {
return app return app
} }
func RunMainApp(app *cli.Command, args ...string) error { func RunMainApp(app *cli.App, args ...string) error {
ctx, cancel := installSignals() err := app.Run(args)
defer cancel()
err := app.Run(ctx, args)
if err == nil { if err == nil {
return nil return nil
} }

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -15,10 +14,9 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@@ -29,11 +27,11 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
} }
func newTestApp(testCmd cli.Command) *cli.Command { func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
app := NewMainApp(AppVersion{}) app := NewMainApp(AppVersion{})
testCmd.Name = util.IfZero(testCmd.Name, "test-cmd") testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithGlobalFlags(&testCmd) prepareSubcommandWithConfig(testCmd, appGlobalFlags())
app.Commands = append(app.Commands, &testCmd) app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name app.DefaultCommand = testCmd.Name
return app return app
} }
@@ -44,7 +42,7 @@ type runResult struct {
ExitCode int ExitCode int
} }
func runTestApp(app *cli.Command, args ...string) (runResult, error) { func runTestApp(app *cli.App, args ...string) (runResult, error) {
outBuf := new(strings.Builder) outBuf := new(strings.Builder)
errBuf := new(strings.Builder) errBuf := new(strings.Builder)
app.Writer = outBuf app.Writer = outBuf
@@ -67,7 +65,7 @@ func TestCliCmd(t *testing.T) {
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)" cli.CommandHelpTemplate = "(command help template)"
cli.RootCommandHelpTemplate = "(app help template)" cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)" cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct { cases := []struct {
@@ -75,56 +73,12 @@ func TestCliCmd(t *testing.T) {
cmd string cmd string
exp string exp string
}{ }{
// help commands // main command help
{
cmd: "./gitea -h",
exp: "DEFAULT CONFIGURATION:",
},
{ {
cmd: "./gitea help", cmd: "./gitea help",
exp: "DEFAULT CONFIGURATION:", exp: "DEFAULT CONFIGURATION:",
}, },
{
cmd: "./gitea -c /dev/null -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea help -c /dev/null",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null test-cmd -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -c /dev/null -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -h -c /dev/null",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null test-cmd help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -c /dev/null help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd help -c /dev/null",
exp: "ConfigFile: /dev/null",
},
// parse paths // parse paths
{ {
cmd: "./gitea test-cmd", cmd: "./gitea test-cmd",
@@ -155,14 +109,12 @@ func TestCliCmd(t *testing.T) {
}, },
} }
app := newTestApp(func(ctx *cli.Context) error {
_, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
})
for _, c := range cases { for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) { t.Run(c.cmd, func(t *testing.T) {
app := newTestApp(cli.Command{
Action: func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
},
})
for k, v := range c.env { for k, v := range c.env {
t.Setenv(k, v) t.Setenv(k, v)
} }
@@ -176,54 +128,31 @@ func TestCliCmd(t *testing.T) {
} }
func TestCliCmdError(t *testing.T) { func TestCliCmdError(t *testing.T) {
app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }}) app := newTestApp(func(ctx *cli.Context) error { return errors.New("normal error") })
r, err := runTestApp(app, "./gitea", "test-cmd") r, err := runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Empty(t, r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr) assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }}) app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) })
r, err = runTestApp(app, "./gitea", "test-cmd") r, err = runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 2, r.ExitCode) assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Empty(t, r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr) assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd") r, err = runTestApp(app, "./gitea", "test-cmd")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
assert.Empty(t, r.Stdout) assert.Empty(t, r.Stdout)
assert.Empty(t, r.Stderr) assert.Empty(t, r.Stderr)
} }
func TestCliCmdBefore(t *testing.T) {
ctxNew := context.WithValue(context.Background(), any("key"), "value")
configValues := map[string]string{}
setting.CustomConf = "/tmp/any.ini"
var actionCtx context.Context
app := newTestApp(cli.Command{
Before: func(context.Context, *cli.Command) (context.Context, error) {
configValues["before"] = setting.CustomConf
return ctxNew, nil
},
Action: func(ctx context.Context, cmd *cli.Command) error {
configValues["action"] = setting.CustomConf
actionCtx = ctx
return nil
},
})
_, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd")
assert.NoError(t, err)
assert.Equal(t, ctxNew, actionCtx)
assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config")
assert.Equal(t, "/dev/null", configValues["action"])
}

View File

@@ -4,13 +4,12 @@
package cmd package cmd
import ( import (
"context"
"os" "os"
"time" "time"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@@ -19,7 +18,7 @@ var (
Name: "manager", Name: "manager",
Usage: "Manage the running gitea process", Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process", Description: "This is a command for managing the running gitea process",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdShutdown, subcmdShutdown,
subcmdRestart, subcmdRestart,
subcmdReloadTemplates, subcmdReloadTemplates,
@@ -109,31 +108,46 @@ var (
} }
) )
func runShutdown(ctx context.Context, c *cli.Command) error { func runShutdown(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.Shutdown(ctx) extra := private.Shutdown(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runRestart(ctx context.Context, c *cli.Command) error { func runRestart(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.Restart(ctx) extra := private.Restart(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runReloadTemplates(ctx context.Context, c *cli.Command) error { func runReloadTemplates(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.ReloadTemplates(ctx) extra := private.ReloadTemplates(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runFlushQueues(ctx context.Context, c *cli.Command) error { func runFlushQueues(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runProcesses(ctx context.Context, c *cli.Command) error { func runProcesses(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)

View File

@@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -12,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@@ -61,7 +60,7 @@ var (
subcmdLogging = &cli.Command{ subcmdLogging = &cli.Command{
Name: "logging", Name: "logging",
Usage: "Adjust logging commands", Usage: "Adjust logging commands",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "pause", Name: "pause",
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
@@ -105,7 +104,7 @@ var (
}, { }, {
Name: "add", Name: "add",
Usage: "Add a logger", Usage: "Add a logger",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "file", Name: "file",
Usage: "Add a file logger", Usage: "Add a file logger",
@@ -119,6 +118,7 @@ var (
Name: "rotate", Name: "rotate",
Aliases: []string{"r"}, Aliases: []string{"r"},
Usage: "Rotate logs", Usage: "Rotate logs",
Value: true,
}, },
&cli.Int64Flag{ &cli.Int64Flag{
Name: "max-size", Name: "max-size",
@@ -129,6 +129,7 @@ var (
Name: "daily", Name: "daily",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "Rotate logs daily", Usage: "Rotate logs daily",
Value: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "max-days", Name: "max-days",
@@ -139,6 +140,7 @@ var (
Name: "compress", Name: "compress",
Aliases: []string{"z"}, Aliases: []string{"z"},
Usage: "Compress rotated logs", Usage: "Compress rotated logs",
Value: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "compression-level", Name: "compression-level",
@@ -193,7 +195,10 @@ var (
} }
) )
func runRemoveLogger(ctx context.Context, c *cli.Command) error { func runRemoveLogger(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
logger := c.String("logger") logger := c.String("logger")
if len(logger) == 0 { if len(logger) == 0 {
@@ -205,7 +210,10 @@ func runRemoveLogger(ctx context.Context, c *cli.Command) error {
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runAddConnLogger(ctx context.Context, c *cli.Command) error { func runAddConnLogger(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
vals := map[string]any{} vals := map[string]any{}
mode := "conn" mode := "conn"
@@ -229,10 +237,13 @@ func runAddConnLogger(ctx context.Context, c *cli.Command) error {
if c.IsSet("reconnect-on-message") { if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
} }
return commonAddLogger(ctx, c, mode, vals) return commonAddLogger(c, mode, vals)
} }
func runAddFileLogger(ctx context.Context, c *cli.Command) error { func runAddFileLogger(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
vals := map[string]any{} vals := map[string]any{}
mode := "file" mode := "file"
@@ -259,10 +270,10 @@ func runAddFileLogger(ctx context.Context, c *cli.Command) error {
if c.IsSet("compression-level") { if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level") vals["compressionLevel"] = c.Int("compression-level")
} }
return commonAddLogger(ctx, c, mode, vals) return commonAddLogger(c, mode, vals)
} }
func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error { func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
if len(c.String("level")) > 0 { if len(c.String("level")) > 0 {
vals["level"] = log.LevelFromString(c.String("level")).String() vals["level"] = log.LevelFromString(c.String("level")).String()
} }
@@ -289,33 +300,46 @@ func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[
if c.IsSet("writer") { if c.IsSet("writer") {
writer = c.String("writer") writer = c.String("writer")
} }
ctx, cancel := installSignals()
defer cancel()
extra := private.AddLogger(ctx, logger, writer, mode, vals) extra := private.AddLogger(ctx, logger, writer, mode, vals)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runPauseLogging(ctx context.Context, c *cli.Command) error { func runPauseLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
userMsg := private.PauseLogging(ctx) userMsg := private.PauseLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runResumeLogging(ctx context.Context, c *cli.Command) error { func runResumeLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
userMsg := private.ResumeLogging(ctx) userMsg := private.ResumeLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error { func runReleaseReopenLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
userMsg := private.ReleaseReopenLogging(ctx) userMsg := private.ReleaseReopenLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runSetLogSQL(ctx context.Context, c *cli.Command) error { func runSetLogSQL(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.SetLogSQL(ctx, !c.Bool("off")) extra := private.SetLogSQL(ctx, !c.Bool("off"))

View File

@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/versioned_migration" "code.gitea.io/gitea/services/versioned_migration"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdMigrate represents the available migrate sub-command. // CmdMigrate represents the available migrate sub-command.
@@ -22,8 +22,11 @@ var CmdMigrate = &cli.Command{
Action: runMigrate, Action: runMigrate,
} }
func runMigrate(ctx context.Context, c *cli.Command) error { func runMigrate(ctx *cli.Context) error {
if err := initDB(ctx); err != nil { stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }

View File

@@ -22,7 +22,7 @@ import (
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/versioned_migration" "code.gitea.io/gitea/services/versioned_migration"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdMigrateStorage represents the available migrate storage sub-command. // CmdMigrateStorage represents the available migrate storage sub-command.
@@ -213,8 +213,11 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
}) })
} }
func runMigrateStorage(ctx context.Context, cmd *cli.Command) error { func runMigrateStorage(ctx *cli.Context) error {
if err := initDB(ctx); err != nil { stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }
@@ -235,51 +238,51 @@ func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
var dstStorage storage.ObjectStorage var dstStorage storage.ObjectStorage
var err error var err error
switch strings.ToLower(cmd.String("storage")) { switch strings.ToLower(ctx.String("storage")) {
case "": case "":
fallthrough fallthrough
case string(setting.LocalStorageType): case string(setting.LocalStorageType):
p := cmd.String("path") p := ctx.String("path")
if p == "" { if p == "" {
log.Fatal("Path must be given when storage is local") log.Fatal("Path must be given when storage is local")
return nil return nil
} }
dstStorage, err = storage.NewLocalStorage( dstStorage, err = storage.NewLocalStorage(
ctx, stdCtx,
&setting.Storage{ &setting.Storage{
Path: p, Path: p,
}) })
case string(setting.MinioStorageType): case string(setting.MinioStorageType):
dstStorage, err = storage.NewMinioStorage( dstStorage, err = storage.NewMinioStorage(
ctx, stdCtx,
&setting.Storage{ &setting.Storage{
MinioConfig: setting.MinioStorageConfig{ MinioConfig: setting.MinioStorageConfig{
Endpoint: cmd.String("minio-endpoint"), Endpoint: ctx.String("minio-endpoint"),
AccessKeyID: cmd.String("minio-access-key-id"), AccessKeyID: ctx.String("minio-access-key-id"),
SecretAccessKey: cmd.String("minio-secret-access-key"), SecretAccessKey: ctx.String("minio-secret-access-key"),
Bucket: cmd.String("minio-bucket"), Bucket: ctx.String("minio-bucket"),
Location: cmd.String("minio-location"), Location: ctx.String("minio-location"),
BasePath: cmd.String("minio-base-path"), BasePath: ctx.String("minio-base-path"),
UseSSL: cmd.Bool("minio-use-ssl"), UseSSL: ctx.Bool("minio-use-ssl"),
InsecureSkipVerify: cmd.Bool("minio-insecure-skip-verify"), InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
ChecksumAlgorithm: cmd.String("minio-checksum-algorithm"), ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
BucketLookUpType: cmd.String("minio-bucket-lookup-type"), BucketLookUpType: ctx.String("minio-bucket-lookup-type"),
}, },
}) })
case string(setting.AzureBlobStorageType): case string(setting.AzureBlobStorageType):
dstStorage, err = storage.NewAzureBlobStorage( dstStorage, err = storage.NewAzureBlobStorage(
ctx, stdCtx,
&setting.Storage{ &setting.Storage{
AzureBlobConfig: setting.AzureBlobStorageConfig{ AzureBlobConfig: setting.AzureBlobStorageConfig{
Endpoint: cmd.String("azureblob-endpoint"), Endpoint: ctx.String("azureblob-endpoint"),
AccountName: cmd.String("azureblob-account-name"), AccountName: ctx.String("azureblob-account-name"),
AccountKey: cmd.String("azureblob-account-key"), AccountKey: ctx.String("azureblob-account-key"),
Container: cmd.String("azureblob-container"), Container: ctx.String("azureblob-container"),
BasePath: cmd.String("azureblob-base-path"), BasePath: ctx.String("azureblob-base-path"),
}, },
}) })
default: default:
return fmt.Errorf("unsupported storage type: %s", cmd.String("storage")) return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
} }
if err != nil { if err != nil {
return err return err
@@ -296,14 +299,14 @@ func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
"actions-artifacts": migrateActionsArtifacts, "actions-artifacts": migrateActionsArtifacts,
} }
tp := strings.ToLower(cmd.String("type")) tp := strings.ToLower(ctx.String("type"))
if m, ok := migratedMethods[tp]; ok { if m, ok := migratedMethods[tp]; ok {
if err := m(ctx, dstStorage); err != nil { if err := m(stdCtx, dstStorage); err != nil {
return err return err
} }
log.Info("%s files have successfully been copied to the new storage.", tp) log.Info("%s files have successfully been copied to the new storage.", tp)
return nil return nil
} }
return fmt.Errorf("unsupported storage: %s", cmd.String("type")) return fmt.Errorf("unsupported storage: %s", ctx.String("type"))
} }

View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@@ -29,7 +30,7 @@ func TestMigratePackages(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
defer buf.Close() defer buf.Close()
v, f, err := packages_service.CreatePackageAndAddFile(t.Context(), &packages_service.PackageCreationInfo{ v, f, err := packages_service.CreatePackageAndAddFile(db.DefaultContext, &packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{ PackageInfo: packages_service.PackageInfo{
Owner: creator, Owner: creator,
PackageType: packages.TypeGeneric, PackageType: packages.TypeGeneric,

View File

@@ -4,13 +4,12 @@
package cmd package cmd
import ( import (
"context"
"strings" "strings"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdRestoreRepository represents the available restore a repository sub-command. // CmdRestoreRepository represents the available restore a repository sub-command.
@@ -49,7 +48,10 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
}, },
} }
func runRestoreRepository(ctx context.Context, c *cli.Command) error { func runRestoreRepository(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setting.MustInstalled() setting.MustInstalled()
var units []string var units []string
if s := c.String("units"); s != "" { if s := c.String("units"); s != "" {

View File

@@ -11,16 +11,17 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time"
"unicode" "unicode"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer" "code.gitea.io/gitea/modules/lfstransfer"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@@ -31,8 +32,17 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/lfs" "code.gitea.io/gitea/services/lfs"
"github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote" "github.com/kballard/go-shellquote"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
)
const (
verbUploadPack = "git-upload-pack"
verbUploadArchive = "git-upload-archive"
verbReceivePack = "git-receive-pack"
verbLfsAuthenticate = "git-lfs-authenticate"
verbLfsTransfer = "git-lfs-transfer"
) )
// CmdServ represents the available serv sub-command. // CmdServ represents the available serv sub-command.
@@ -40,7 +50,6 @@ var CmdServ = &cli.Command{
Name: "serv", Name: "serv",
Usage: "(internal) Should only be called by SSH shell", Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories", Description: "Serv provides access auth for repositories",
Hidden: true, // Internal commands shouldn't be visible in help
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ, Action: runServ,
Flags: []cli.Flag{ Flags: []cli.Flag{
@@ -64,11 +73,27 @@ func setup(ctx context.Context, debug bool) {
_ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err) _ = fail(ctx, "Unable to access repository path", "Unable to access repository path %q, err: %v", setting.RepoRootPath, err)
return return
} }
if err := git.InitSimple(); err != nil { if err := git.InitSimple(context.Background()); err != nil {
_ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err) _ = fail(ctx, "Failed to init git", "Failed to init git, err: %v", err)
} }
} }
var (
// keep getAccessMode() in sync
allowedCommands = container.SetOf(
verbUploadPack,
verbUploadArchive,
verbReceivePack,
verbLfsAuthenticate,
verbLfsTransfer,
)
allowedCommandsLfs = container.SetOf(
verbLfsAuthenticate,
verbLfsTransfer,
)
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
)
// fail prints message to stdout, it's mainly used for git serv and git hook commands. // fail prints message to stdout, it's mainly used for git serv and git hook commands.
// The output will be passed to git client and shown to user. // The output will be passed to git client and shown to user.
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error { func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
@@ -114,24 +139,47 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
func getAccessMode(verb, lfsVerb string) perm.AccessMode { func getAccessMode(verb, lfsVerb string) perm.AccessMode {
switch verb { switch verb {
case git.CmdVerbUploadPack, git.CmdVerbUploadArchive: case verbUploadPack, verbUploadArchive:
return perm.AccessModeRead return perm.AccessModeRead
case git.CmdVerbReceivePack: case verbReceivePack:
return perm.AccessModeWrite return perm.AccessModeWrite
case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer: case verbLfsAuthenticate, verbLfsTransfer:
switch lfsVerb { switch lfsVerb {
case git.CmdSubVerbLfsUpload: case "upload":
return perm.AccessModeWrite return perm.AccessModeWrite
case git.CmdSubVerbLfsDownload: case "download":
return perm.AccessModeRead return perm.AccessModeRead
} }
} }
// should be unreachable // should be unreachable
setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
return perm.AccessModeNone return perm.AccessModeNone
} }
func runServ(ctx context.Context, c *cli.Command) error { func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
now := time.Now()
claims := lfs.Claims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
NotBefore: jwt.NewNumericDate(now),
},
RepoID: results.RepoID,
Op: lfsVerb,
UserID: results.UserID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
if err != nil {
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
}
return "Bearer " + tokenString, nil
}
func runServ(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
// FIXME: This needs to internationalised // FIXME: This needs to internationalised
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
@@ -182,32 +230,41 @@ func runServ(ctx context.Context, c *cli.Command) error {
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND")) log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
} }
sshCmdArgs, err := shellquote.Split(cmd) words, err := shellquote.Split(cmd)
if err != nil { if err != nil {
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err) return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
} }
if len(sshCmdArgs) < 2 { if len(words) < 2 {
if git.DefaultFeatures().SupportProcReceive { if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow // for AGit Flow
if cmd == "ssh_info" { if cmd == "ssh_info" {
fmt.Print(`{"type":"agit","version":1}`) fmt.Print(`{"type":"gitea","version":1}`)
return nil return nil
} }
} }
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
} }
repoPath := strings.TrimPrefix(sshCmdArgs[1], "/") verb := words[0]
repoPathFields := strings.SplitN(repoPath, "/", 2) repoPath := strings.TrimPrefix(words[1], "/")
if len(repoPathFields) != 2 {
var lfsVerb string
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
} }
username := repoPathFields[0] username := rr[0]
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki" reponame := strings.TrimSuffix(rr[1], ".git")
if !repo_model.IsValidSSHAccessRepoName(reponame) { // 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) return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
} }
@@ -229,22 +286,21 @@ func runServ(ctx context.Context, c *cli.Command) error {
}() }()
} }
verb, lfsVerb := sshCmdArgs[0], "" if allowedCommands.Contains(verb) {
if !git.IsAllowedVerbForServe(verb) { if allowedCommandsLfs.Contains(verb) {
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
}
if git.IsAllowedVerbForServeLfs(verb) {
if !setting.LFS.StartServer { if !setting.LFS.StartServer {
return fail(ctx, "LFS Server is not enabled", "") return fail(ctx, "LFS Server is not enabled", "")
} }
if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH { if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
return fail(ctx, "LFS SSH transfer is not enabled", "") return fail(ctx, "LFS SSH transfer is not enabled", "")
} }
if len(sshCmdArgs) > 2 { if len(words) > 2 {
lfsVerb = sshCmdArgs[2] lfsVerb = words[2]
} }
} }
} else {
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
}
requestedMode := getAccessMode(verb, lfsVerb) requestedMode := getAccessMode(verb, lfsVerb)
@@ -253,16 +309,9 @@ func runServ(ctx context.Context, c *cli.Command) error {
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error) return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
} }
// because the original repoPath maybe redirected, we need to use the returned actual repository information
if results.IsWiki {
repoPath = repo_model.RelativeWikiPath(results.OwnerName, results.RepoName)
} else {
repoPath = repo_model.RelativePath(results.OwnerName, results.RepoName)
}
// LFS SSH protocol // LFS SSH protocol
if verb == git.CmdVerbLfsTransfer { if verb == verbLfsTransfer {
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID}) token, err := getLFSAuthToken(ctx, lfsVerb, results)
if err != nil { if err != nil {
return err return err
} }
@@ -270,10 +319,10 @@ func runServ(ctx context.Context, c *cli.Command) error {
} }
// LFS token authentication // LFS token authentication
if verb == git.CmdVerbLfsAuthenticate { if verb == verbLfsAuthenticate {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName)) url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID}) token, err := getLFSAuthToken(ctx, lfsVerb, results)
if err != nil { if err != nil {
return err return err
} }
@@ -292,8 +341,8 @@ func runServ(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
var command *exec.Cmd var gitcmd *exec.Cmd
gitBinPath := filepath.Dir(gitcmd.GitExecutable) // e.g. /usr/bin gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
if _, err := os.Stat(gitBinVerb); err != nil { if _, err := os.Stat(gitBinVerb); err != nil {
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git // if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
@@ -301,21 +350,21 @@ func runServ(ctx context.Context, c *cli.Command) error {
verbFields := strings.SplitN(verb, "-", 2) verbFields := strings.SplitN(verb, "-", 2)
if len(verbFields) == 2 { if len(verbFields) == 2 {
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ... // use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
command = exec.CommandContext(ctx, gitcmd.GitExecutable, verbFields[1], repoPath) gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
} }
} }
if command == nil { if gitcmd == nil {
// by default, use the verb (it has been checked above by allowedCommands) // by default, use the verb (it has been checked above by allowedCommands)
command = exec.CommandContext(ctx, gitBinVerb, repoPath) gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
} }
process.SetSysProcAttribute(command) process.SetSysProcAttribute(gitcmd)
command.Dir = setting.RepoRootPath gitcmd.Dir = setting.RepoRootPath
command.Stdout = os.Stdout gitcmd.Stdout = os.Stdout
command.Stdin = os.Stdin gitcmd.Stdin = os.Stdin
command.Stderr = os.Stderr gitcmd.Stderr = os.Stderr
command.Env = append(command.Env, os.Environ()...) gitcmd.Env = append(gitcmd.Env, os.Environ()...)
command.Env = append(command.Env, gitcmd.Env = append(gitcmd.Env,
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki), repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
repo_module.EnvRepoName+"="+results.RepoName, repo_module.EnvRepoName+"="+results.RepoName,
repo_module.EnvRepoUsername+"="+results.OwnerName, repo_module.EnvRepoUsername+"="+results.OwnerName,
@@ -330,9 +379,9 @@ func runServ(ctx context.Context, c *cli.Command) error {
) )
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command. // to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later. // it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
command.Env = append(command.Env, gitcmd.CommonCmdServEnvs()...) gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...)
if err = command.Run(); err != nil { if err = gitcmd.Run(); err != nil {
return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err) return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err)
} }

View File

@@ -28,7 +28,7 @@ import (
"code.gitea.io/gitea/routers/install" "code.gitea.io/gitea/routers/install"
"github.com/felixge/fgprof" "github.com/felixge/fgprof"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// PIDFile could be set from build tag // PIDFile could be set from build tag
@@ -130,19 +130,19 @@ func showWebStartupMessage(msg string) {
} }
} }
func serveInstall(cmd *cli.Command) error { func serveInstall(ctx *cli.Context) error {
showWebStartupMessage("Prepare to run install page") showWebStartupMessage("Prepare to run install page")
routers.InitWebInstallPage(graceful.GetManager().HammerContext()) routers.InitWebInstallPage(graceful.GetManager().HammerContext())
// Flag for port number in case first time run conflict // Flag for port number in case first time run conflict
if cmd.IsSet("port") { if ctx.IsSet("port") {
if err := setPort(cmd.String("port")); err != nil { if err := setPort(ctx.String("port")); err != nil {
return err return err
} }
} }
if cmd.IsSet("install-port") { if ctx.IsSet("install-port") {
if err := setPort(cmd.String("install-port")); err != nil { if err := setPort(ctx.String("install-port")); err != nil {
return err return err
} }
} }
@@ -163,7 +163,7 @@ func serveInstall(cmd *cli.Command) error {
return nil return nil
} }
func serveInstalled(c *cli.Command) error { func serveInstalled(ctx *cli.Context) error {
setting.InitCfgProvider(setting.CustomConf) setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings() setting.LoadCommonSettings()
setting.MustInstalled() setting.MustInstalled()
@@ -218,8 +218,8 @@ func serveInstalled(c *cli.Command) error {
setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour) setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour)
// Override the provided port number within the configuration // Override the provided port number within the configuration
if c.IsSet("port") { if ctx.IsSet("port") {
if err := setPort(c.String("port")); err != nil { if err := setPort(ctx.String("port")); err != nil {
return err return err
} }
} }
@@ -236,27 +236,22 @@ func serveInstalled(c *cli.Command) error {
} }
func servePprof() { func servePprof() {
// FIXME: it shouldn't use the global DefaultServeMux, and it should use a proper context
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler()) http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
_, _, finished := process.GetManager().AddTypedContext(context.TODO(), "Web: PProf Server", process.SystemProcessType, true) _, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment, it's not worth introducing a configurable option for it. // The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
log.Info("Starting pprof server on localhost:6060") log.Info("Starting pprof server on localhost:6060")
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil)) log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
finished() finished()
} }
func runWeb(ctx context.Context, cmd *cli.Command) error { func runWeb(ctx *cli.Context) error {
defer func() { defer func() {
if panicked := recover(); panicked != nil { if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
} }
}() }()
if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid { managerCtx, cancel := context.WithCancel(context.Background())
return fmt.Errorf("unknown command: %s", subCmdName)
}
managerCtx, cancel := context.WithCancel(ctx)
graceful.InitManager(managerCtx) graceful.InitManager(managerCtx)
defer cancel() defer cancel()
@@ -267,12 +262,12 @@ func runWeb(ctx context.Context, cmd *cli.Command) error {
} }
// Set pid file setting // Set pid file setting
if cmd.IsSet("pid") { if ctx.IsSet("pid") {
createPIDFile(cmd.String("pid")) createPIDFile(ctx.String("pid"))
} }
if !setting.InstallLock { if !setting.InstallLock {
if err := serveInstall(cmd); err != nil { if err := serveInstall(ctx); err != nil {
return err return err
} }
} else { } else {
@@ -283,7 +278,7 @@ func runWeb(ctx context.Context, cmd *cli.Command) error {
go servePprof() go servePprof()
} }
return serveInstalled(cmd) return serveInstalled(ctx)
} }
func setPort(port string) error { func setPort(port string) error {

View File

@@ -23,6 +23,12 @@ func NoHTTPRedirector() {
graceful.GetManager().InformCleanup() graceful.GetManager().InformCleanup()
} }
// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener
// for our main HTTP/HTTPS service
func NoMainListener() {
graceful.GetManager().InformCleanup()
}
// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener // NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener
// for our install HTTP/HTTPS service // for our install HTTP/HTTPS service
func NoInstallListener() { func NoInstallListener() {

View File

@@ -0,0 +1,17 @@
Bash and Zsh completion
=======================
From within the gitea root run:
```bash
source contrib/autocompletion/bash_autocomplete
```
or for zsh run:
```bash
source contrib/autocompletion/zsh_autocomplete
```
These scripts will check if gitea is on the path and if so add autocompletion for `gitea`. Or if not autocompletion will work for `./gitea`.
If gitea has been installed as a different program pass in the `PROG` environment variable to set the correct program name.

View File

@@ -0,0 +1,30 @@
#! /bin/bash
# Heavily inspired by https://github.com/urfave/cli
_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
if [ -z "$PROG" ] && [ ! "$(command -v gitea &> /dev/null)" ] ; then
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete gitea
elif [ -z "$PROG" ]; then
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete ./gitea
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PWD/gitea"
else
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete "$PROG"
unset PROG
fi

View File

@@ -0,0 +1,30 @@
#compdef ${PROG:=gitea}
# Heavily inspired by https://github.com/urfave/cli
_cli_zsh_autocomplete() {
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
else
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
return
}
if [ -z $PROG ] ; then
compdef _cli_zsh_autocomplete gitea
else
compdef _cli_zsh_autocomplete $(basename $PROG)
fi

View File

@@ -11,7 +11,7 @@ The default version will read from `docs/config.yml`. You can override this
using the option `--version`. using the option `--version`.
The upstream branches will be fetched, using the remote `origin`. This can The upstream branches will be fetched, using the remote `origin`. This can
be overridden using `--upstream`, and fetching can be avoided using be overrided using `--upstream`, and fetching can be avoided using
`--no-fetch`. `--no-fetch`.
By default the branch created will be called `backport-$PR-$VERSION`. You By default the branch created will be called `backport-$PR-$VERSION`. You

View File

@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//nolint:forbidigo // use of print functions is allowed in cli //nolint:forbidigo
package main package main
import ( import (
@@ -12,19 +12,21 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path" "path"
"strconv" "strconv"
"strings" "strings"
"syscall"
"github.com/google/go-github/v74/github" "github.com/google/go-github/v61/github"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const defaultVersion = "v1.18" // to backport to const defaultVersion = "v1.18" // to backport to
func main() { func main() {
app := &cli.Command{} app := cli.NewApp()
app.Name = "backport" app.Name = "backport"
app.Usage = "Backport provided PR-number on to the current or previous released version" app.Usage = "Backport provided PR-number on to the current or previous released version"
app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version` app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version`
@@ -89,7 +91,7 @@ func main() {
Usage: "Set this flag to continue from a git cherry-pick that has broken", Usage: "Set this flag to continue from a git cherry-pick that has broken",
}, },
} }
cli.RootCommandHelpTemplate = `NAME: cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
@@ -103,12 +105,16 @@ OPTIONS:
` `
app.Action = runBackport app.Action = runBackport
if err := app.Run(context.Background(), os.Args); err != nil {
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err) fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
} }
} }
func runBackport(ctx context.Context, c *cli.Command) error { func runBackport(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
continuing := c.Bool("continue") continuing := c.Bool("continue")
var pr string var pr string
@@ -337,8 +343,8 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out)) fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
return "", "", fmt.Errorf("unable to determine forked remote: %w", err) return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
} }
lines := strings.SplitSeq(string(out), "\n") lines := strings.Split(string(out), "\n")
for line := range lines { for _, line := range lines {
fields := strings.Split(line, "\t") fields := strings.Split(line, "\t")
name, remote := fields[0], fields[1] name, remote := fields[0], fields[1]
// only look at pushers // only look at pushers
@@ -356,12 +362,12 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
if !strings.Contains(remote, forkUser) { if !strings.Contains(remote, forkUser) {
continue continue
} }
if after, ok := strings.CutPrefix(remote, "git@github.com:"); ok { if strings.HasPrefix(remote, "git@github.com:") {
forkUser = after forkUser = strings.TrimPrefix(remote, "git@github.com:")
} else if after, ok := strings.CutPrefix(remote, "https://github.com/"); ok { } else if strings.HasPrefix(remote, "https://github.com/") {
forkUser = after forkUser = strings.TrimPrefix(remote, "https://github.com/")
} else if after, ok := strings.CutPrefix(remote, "https://www.github.com/"); ok { } else if strings.HasPrefix(remote, "https://www.github.com/") {
forkUser = after forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
} else if forkUser == "" { } else if forkUser == "" {
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote) return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
} }
@@ -454,3 +460,25 @@ func determineSHAforPR(ctx context.Context, prStr, accessToken string) (string,
return "", nil return "", nil
} }
func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// install notify
signalChannel := make(chan os.Signal, 1)
signal.Notify(
signalChannel,
syscall.SIGINT,
syscall.SIGTERM,
)
select {
case <-signalChannel:
case <-ctx.Done():
}
cancel()
signal.Reset()
}()
return ctx, cancel
}

View File

@@ -4,17 +4,16 @@
package main package main
import ( import (
"context"
"os" "os"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func main() { func main() {
app := cli.Command{} app := cli.NewApp()
app.Name = "environment-to-ini" app.Name = "environment-to-ini"
app.Usage = "Use provided environment to update configuration ini" app.Usage = "Use provided environment to update configuration ini"
app.Description = `As a helper to allow docker users to update the gitea configuration app.Description = `As a helper to allow docker users to update the gitea configuration
@@ -73,13 +72,13 @@ func main() {
}, },
} }
app.Action = runEnvironmentToIni app.Action = runEnvironmentToIni
err := app.Run(context.Background(), os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
log.Fatal("Failed to run app with %s: %v", os.Args, err) log.Fatal("Failed to run app with %s: %v", os.Args, err)
} }
} }
func runEnvironmentToIni(_ context.Context, c *cli.Command) error { func runEnvironmentToIni(c *cli.Context) error {
// the config system may change the environment variables, so get a copy first, to be used later // the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...) env := append([]string{}, os.Environ()...)
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{ setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{

View File

@@ -150,7 +150,7 @@
<p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p> <p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p>
<p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the basis of consent within 30 days.</p> <p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the bassis of consent within 30 days.</p>
<p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p> <p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p>

View File

@@ -85,7 +85,7 @@ fi
# confirm update # confirm update
echo "Checking currently installed version..." echo "Checking currently installed version..."
current=$(giteacmd --version | cut -d ' ' -f 3) current=$(giteacmd --version | cut -d ' ' -f 3)
[[ "$current" == "$giteaversion" ]] && echo "$current is already installed, stopping." && exit 0 [[ "$current" == "$giteaversion" ]] && echo "$current is already installed, stopping." && exit 1
if [[ -z "${no_confirm:-}" ]]; then if [[ -z "${no_confirm:-}" ]]; then
echo "Make sure to read the changelog first: https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md" echo "Make sure to read the changelog first: https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md"
echo "Are you ready to update Gitea from ${current} to ${giteaversion}? (y/N)" echo "Are you ready to update Gitea from ${current} to ${giteaversion}? (y/N)"

View File

@@ -186,13 +186,17 @@ RUN_USER = ; git
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. ;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true ;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
;; ;;
;; For the builtin SSH server, choose the supported ciphers/key-exchange-algorithms/MACs for SSH connections. ;; For the built-in SSH server, choose the ciphers to support for SSH connections,
;; The supported names are listed in https://github.com/golang/crypto/blob/master/ssh/common.go. ;; for system SSH this setting has no effect
;; Leave them empty to use the Golang crypto's recommended default values. ;SSH_SERVER_CIPHERS = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
;; For system SSH (non-builtin SSH server), this setting has no effect. ;;
;SSH_SERVER_CIPHERS = ;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
;SSH_SERVER_KEY_EXCHANGES = ;; for system SSH this setting has no effect
;SSH_SERVER_MACS = ;SSH_SERVER_KEY_EXCHANGES = curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
;;
;; For the built-in SSH server, choose the MACs to support for SSH connections,
;; for system SSH this setting has no effect
;SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1
;; ;;
;; For the built-in SSH server, choose the keypair to offer as the host key ;; For the built-in SSH server, choose the keypair to offer as the host key
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub ;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
@@ -567,11 +571,6 @@ ENABLED = true
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one ;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret ;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
;; ;;
;; The "issuer" claim identifies the principal that issued the JWT.
;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
;JWT_CLAIM_ISSUER =
;;
;; Lifetime of an OAuth2 access token in seconds ;; Lifetime of an OAuth2 access token in seconds
;ACCESS_TOKEN_EXPIRATION_TIME = 3600 ;ACCESS_TOKEN_EXPIRATION_TIME = 3600
;; ;;
@@ -1191,24 +1190,17 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; GPG or SSH key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey ;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
;; Depending on the value of SIGNING_FORMAT this is either:
;; - openpgp: the GPG key ID
;; - ssh: the path to the ssh public key "/path/to/key.pub": where "/path/to/key" is the private key, use ssh-keygen -t ed25519 to generate a new key pair without password
;; run in the context of the RUN_USER ;; run in the context of the RUN_USER
;; Switch to none to stop signing completely ;; Switch to none to stop signing completely
;SIGNING_KEY = default ;SIGNING_KEY = default
;; ;;
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer and the signing format. ;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to ;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to
;; the results of git config --get user.name, git config --get user.email and git config --default openpgp --get gpg.format respectively and can only be overridden ;; the results of git config --get user.name and git config --get user.email respectively and can only be overridden
;; by setting the SIGNING_KEY ID to the correct ID.) ;; by setting the SIGNING_KEY ID to the correct ID.)
;SIGNING_NAME = ;SIGNING_NAME =
;SIGNING_EMAIL = ;SIGNING_EMAIL =
;; SIGNING_FORMAT can be one of:
;; - openpgp (default): use GPG to sign commits
;; - ssh: use SSH to sign commits
;SIGNING_FORMAT = openpgp
;; ;;
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter ;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
;DEFAULT_TRUST_MODEL = collaborator ;DEFAULT_TRUST_MODEL = collaborator
@@ -1235,13 +1227,6 @@ LEVEL = Info
;; - commitssigned: require that all the commits in the head branch are signed. ;; - commitssigned: require that all the commits in the head branch are signed.
;; - approved: only sign when merging an approved pr to a protected branch ;; - approved: only sign when merging an approved pr to a protected branch
;MERGES = pubkey, twofa, basesigned, commitssigned ;MERGES = pubkey, twofa, basesigned, commitssigned
;;
;; Determines which additional ssh keys are trusted for all signed commits regardless of the user
;; This is useful for ssh signing key rotation.
;; Exposes the provided SIGNING_NAME and SIGNING_EMAIL as the signer, regardless of the SIGNING_FORMAT value.
;; Multiple keys should be comma separated.
;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
;TRUSTED_SSH_KEYS =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1348,10 +1333,6 @@ LEVEL = Info
;; Dont mistake it for Reactions. ;; Dont mistake it for Reactions.
;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs ;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs
;; ;;
;; Comma separated list of enabled emojis, for example: smile, thumbsup, thumbsdown
;; Leave it empty to enable all emojis.
;ENABLED_EMOJIS =
;;
;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. ;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
;DEFAULT_SHOW_FULL_NAME = false ;DEFAULT_SHOW_FULL_NAME = false
;; ;;
@@ -2545,19 +2526,7 @@ LEVEL = Info
;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] . ;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] .
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code. ;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page. ;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
;RENDER_CONTENT_MODE = sanitized ;RENDER_CONTENT_MODE=sanitized
;; The sandbox applied to the iframe and Content-Security-Policy header when RENDER_CONTENT_MODE is `iframe`.
;; It defaults to a safe set of "allow-*" restrictions (space separated).
;; You can also set it by your requirements or use "disabled" to disable the sandbox completely.
;; When set it, make sure there is no security risk:
;; * PDF-only content: generally safe to use "disabled", and it needs to be "disabled" because PDF only renders with no sandbox.
;; * HTML content with JS: if the "RENDER_COMMAND" can guarantee there is no XSS, then it is safe, otherwise, you need to fine tune the "allow-*" restrictions.
;RENDER_CONTENT_SANDBOX =
;; Whether post-process the rendered HTML content, including:
;; resolve relative links and image sources, recognizing issue/commit references, escaping invisible characters,
;; mentioning users, rendering permlink code blocks, replacing emoji shorthands, etc.
;; By default, this is true when RENDER_CONTENT_MODE is `sanitized`, otherwise false.
;NEED_POST_PROCESS = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

File diff suppressed because it is too large Load Diff

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1755186698, "lastModified": 1739214665,
"narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", "narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", "rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -11,28 +11,8 @@
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
in in
{ {
devShells.default = devShells.default = pkgs.mkShell {
with pkgs; buildInputs = with pkgs; [
let
# only bump toolchain versions here
go = go_1_25;
nodejs = nodejs_24;
python3 = python312;
pnpm = pnpm_10;
# Platform-specific dependencies
linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [
glibc.static
];
linuxOnlyEnv = lib.optionalAttrs pkgs.stdenv.isLinux {
CFLAGS = "-I${glibc.static.dev}/include";
LDFLAGS = "-L ${glibc.static}/lib";
};
in
pkgs.mkShell (
{
buildInputs = [
# generic # generic
git git
git-lfs git-lfs
@@ -40,34 +20,24 @@
gnused gnused
gnutar gnutar
gzip gzip
zip
# frontend # frontend
nodejs nodejs_22
pnpm
cairo
pixman
pkg-config
# linting # linting
python3 python312
uv poetry
# backend # backend
go go_1_24
gofumpt gofumpt
sqlite sqlite
] ];
++ linuxOnlyInputs; shellHook = ''
export GO="${pkgs.go_1_24}/bin/go"
GO = "${go}/bin/go"; export GOROOT="${pkgs.go_1_24}/share/go"
GOROOT = "${go}/share/go"; '';
};
TAGS = "sqlite sqlite_unlock_notify";
STATIC = "true";
}
// linuxOnlyEnv
);
} }
); );
} }

261
go.mod
View File

@@ -1,8 +1,6 @@
module code.gitea.io/gitea module code.gitea.io/gitea
go 1.25.0 go 1.24
toolchain go1.25.5
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related: // But some CAs use negative serial number, just relax the check. related:
@@ -12,7 +10,7 @@ godebug x509negativeserial=1
require ( require (
code.gitea.io/actions-proto-go v0.4.1 code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/gitea-vet v0.2.3 code.gitea.io/gitea-vet v0.2.3
code.gitea.io/sdk/gitea v0.22.0 code.gitea.io/sdk/gitea v0.21.0
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.18.1 connectrpc.com/connect v1.18.1
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
@@ -21,28 +19,28 @@ require (
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/httpsig v1.2.3 github.com/42wim/httpsig v1.2.2
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920 github.com/42wim/sshsig v0.0.0-20240818000253-e3a6333df815
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.3.0 github.com/ProtonMail/go-crypto v1.2.0
github.com/PuerkitoBio/goquery v1.10.3 github.com/PuerkitoBio/goquery v1.10.3
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
github.com/alecthomas/chroma/v2 v2.20.0 github.com/alecthomas/chroma/v2 v2.17.0
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 github.com/aws/aws-sdk-go-v2/credentials v1.17.67
github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2 github.com/aws/aws-sdk-go-v2/service/codecommit v1.28.2
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.3 github.com/blevesearch/bleve/v2 v2.5.0
github.com/bohde/codel v0.2.0 github.com/bohde/codel v0.2.0
github.com/buildkite/terminal-to-html/v3 v3.16.8 github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.24.0 github.com/caddyserver/certmagic v0.23.0
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 github.com/charmbracelet/git-lfs-transfer v0.2.0
github.com/chi-middleware/proxy v1.1.1 github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0 github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1 github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap v1.2.1
@@ -51,24 +49,26 @@ require (
github.com/felixge/fgprof v0.9.5 github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/gliderlabs/ssh v0.3.8 github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742 github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2 github.com/go-enry/go-enry/v2 v2.9.2
github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.3 github.com/go-git/go-git/v5 v5.16.0
github.com/go-ldap/ldap/v3 v3.4.11 github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.9.3 github.com/go-sql-driver/mysql v1.9.2
github.com/go-webauthn/webauthn v0.13.4 github.com/go-swagger/go-swagger v0.31.0
github.com/go-webauthn/webauthn v0.12.3
github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/go-github/v74 v74.0.0 github.com/google/go-github/v61 v61.0.0
github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 github.com/google/pprof v0.0.0-20250422154841-e1f9c1950416
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0 github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
@@ -80,103 +80,104 @@ require (
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.0 github.com/klauspost/compress v1.18.0
github.com/klauspost/cpuid/v2 v2.3.0 github.com/klauspost/cpuid/v2 v2.2.10
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/markbates/goth v1.82.0 github.com/markbates/goth v1.81.0
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.32 github.com/mattn/go-sqlite3 v1.14.28
github.com/meilisearch/meilisearch-go v0.33.2 github.com/meilisearch/meilisearch-go v0.31.0
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.9.3 github.com/microsoft/go-mssqldb v1.8.0
github.com/minio/minio-go/v7 v7.0.95 github.com/minio/minio-go/v7 v7.0.91
github.com/msteinert/pam v1.2.0 github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.63 github.com/nektos/act v0.2.63
github.com/niklasfasching/go-org v1.9.1 github.com/niklasfasching/go-org v1.7.0
github.com/olivere/elastic/v7 v7.0.32 github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.5.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_golang v1.22.0
github.com/quasoft/websspi v1.1.2 github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.12.1 github.com/redis/go-redis/v9 v9.7.3
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.4.0 github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.4.0 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/stretchr/testify v1.11.1 github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0 github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.15 github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli-docs/v3 v3.0.0-alpha6 github.com/urfave/cli/v2 v2.27.6
github.com/urfave/cli/v3 v3.4.1 github.com/wneessen/go-mail v0.6.2
github.com/wneessen/go-mail v0.7.2
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.13 github.com/yuin/goldmark v1.7.10
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.142.4 gitlab.com/gitlab-org/api/client-go v0.127.0
golang.org/x/crypto v0.45.0 golang.org/x/crypto v0.37.0
golang.org/x/image v0.30.0 golang.org/x/image v0.26.0
golang.org/x/net v0.47.0 golang.org/x/net v0.39.0
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.18.0 golang.org/x/sync v0.13.0
golang.org/x/sys v0.38.0 golang.org/x/sys v0.32.0
golang.org/x/text v0.31.0 golang.org/x/text v0.24.0
google.golang.org/grpc v1.75.0 golang.org/x/tools v0.32.0
google.golang.org/protobuf v1.36.8 google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.6.0 mvdan.cc/xurls/v2 v2.6.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.13 xorm.io/builder v0.3.13
xorm.io/xorm v1.3.10 xorm.io/xorm v1.3.9
) )
require ( require (
cloud.google.com/go/compute/metadata v0.8.0 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect
dario.cat/mergo v1.0.2 // indirect dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/DataDog/zstd v1.5.7 // indirect github.com/DataDog/zstd v1.5.7 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/smithy-go v1.23.0 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.0 // indirect github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.9 // indirect github.com/blevesearch/bleve_index_api v1.2.8 // indirect
github.com/blevesearch/geo v0.2.4 // indirect github.com/blevesearch/geo v0.2.0 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.11 // indirect github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect
github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect github.com/blevesearch/zapx/v11 v11.4.1 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect github.com/blevesearch/zapx/v12 v12.4.1 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect github.com/blevesearch/zapx/v13 v13.4.1 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect github.com/blevesearch/zapx/v14 v14.4.1 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect github.com/blevesearch/zapx/v15 v15.4.1 // indirect
github.com/blevesearch/zapx/v16 v16.2.4 // indirect github.com/blevesearch/zapx/v16 v16.2.3 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect github.com/boombuler/barcode v1.0.2 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
@@ -185,7 +186,7 @@ require (
github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect github.com/couchbase/gomemcached v0.3.3 // indirect
github.com/couchbase/goutils v0.1.2 // indirect github.com/couchbase/goutils v0.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
@@ -193,15 +194,28 @@ require (
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd // indirect github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-webauthn/x v0.1.24 // indirect github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.1 // indirect
github.com/go-openapi/inflect v0.21.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/runtime v0.28.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-webauthn/x v0.1.20 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
@@ -212,59 +226,69 @@ require (
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.5 // indirect github.com/google/go-tpm v0.9.3 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jessevdk/go-flags v1.6.1 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/libdns/libdns v1.0.0-beta.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/markbates/going v1.0.3 // indirect github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.68 // indirect github.com/miekg/dns v1.1.65 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect github.com/nwaples/rardecode v1.1.3 // indirect
github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/errors v1.1.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/olekukonko/ll v0.1.0 // indirect
github.com/olekukonko/tablewriter v1.0.9 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/philhofer/fwd v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect github.com/rhysd/actionlint v1.7.7 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/xid v1.6.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.15.0 // indirect github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/viper v1.20.1 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/tinylib/msgp v1.4.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/toqueteos/webbrowser v1.2.0 // indirect
github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
@@ -272,33 +296,34 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/assert v1.3.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.3 // indirect go.etcd.io/bbolt v1.4.0 // indirect
go.mongodb.org/mongo-driver v1.17.3 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.29.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.38.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
ignore (
./.venv
./node_modules
)
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/nektos/act => gitea.com/gitea/act v0.261.4
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078

704
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ import (
_ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode" _ "code.gitea.io/gitea/modules/markup/orgmode"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// these flags will be set by the build flags // these flags will be set by the build flags

View File

@@ -13,8 +13,6 @@ func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{ unittest.MainTest(m, &unittest.TestOptions{
FixtureFiles: []string{ FixtureFiles: []string{
"action_runner_token.yml", "action_runner_token.yml",
"action_run.yml",
"repository.yml",
}, },
}) })
} }

View File

@@ -16,7 +16,6 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@@ -166,25 +165,12 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
return nil, fmt.Errorf("event %s is not a pull request event", run.Event) return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
} }
func (run *ActionRun) GetWorkflowRunEventPayload() (*api.WorkflowRunPayload, error) {
if run.Event == webhook_module.HookEventWorkflowRun {
var payload api.WorkflowRunPayload
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
return nil, err
}
return &payload, nil
}
return nil, fmt.Errorf("event %s is not a workflow run event", run.Event)
}
func (run *ActionRun) IsSchedule() bool { func (run *ActionRun) IsSchedule() bool {
return run.ScheduleID > 0 return run.ScheduleID > 0
} }
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID). _, err := db.GetEngine(ctx).ID(repo.ID).
NoAutoTime().
Cols("num_action_runs", "num_closed_action_runs").
SetExpr("num_action_runs", SetExpr("num_action_runs",
builder.Select("count(*)").From("action_run"). builder.Select("count(*)").From("action_run").
Where(builder.Eq{"repo_id": repo.ID}), Where(builder.Eq{"repo_id": repo.ID}),
@@ -283,7 +269,12 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
// InsertRun inserts a run // InsertRun inserts a run
// The title will be cut off at 255 characters if it's longer than 255 characters. // The title will be cut off at 255 characters if it's longer than 255 characters.
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error { func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
return db.WithTx(ctx, func(ctx context.Context) error { ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID) index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
if err != nil { if err != nil {
return err return err
@@ -347,17 +338,17 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return err return err
} }
} }
return nil
}) return committer.Commit()
} }
func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) { func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
var run ActionRun var run ActionRun
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", runID, repoID).Get(&run) has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, fmt.Errorf("run with id %d: %w", runID, util.ErrNotExist) return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
} }
return &run, nil return &run, nil
@@ -428,11 +419,18 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
if run.Status != 0 || slices.Contains(cols, "status") { if run.Status != 0 || slices.Contains(cols, "status") {
if run.RepoID == 0 { if run.RepoID == 0 {
setting.PanicInDevOrTesting("RepoID should not be 0") run, err = GetRunByID(ctx, run.ID)
} if err != nil {
if err = run.LoadRepo(ctx); err != nil {
return err return err
} }
}
if run.Repo == nil {
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil { if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err return err
} }

View File

@@ -51,7 +51,7 @@ func (job *ActionRunJob) Duration() time.Duration {
func (job *ActionRunJob) LoadRun(ctx context.Context) error { func (job *ActionRunJob) LoadRun(ctx context.Context) error {
if job.Run == nil { if job.Run == nil {
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID) run, err := GetRunByID(ctx, job.RunID)
if err != nil { if err != nil {
return err return err
} }
@@ -142,7 +142,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
{ {
// Other goroutines may aggregate the status of the run and update it too. // Other goroutines may aggregate the status of the run and update it too.
// So we need load the run and its jobs before updating the run. // So we need load the run and its jobs before updating the run.
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID) run, err := GetRunByID(ctx, job.RunID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -185,12 +185,12 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
return StatusSuccess return StatusSuccess
case hasCancelled: case hasCancelled:
return StatusCancelled return StatusCancelled
case hasFailure:
return StatusFailure
case hasRunning: case hasRunning:
return StatusRunning return StatusRunning
case hasWaiting: case hasWaiting:
return StatusWaiting return StatusWaiting
case hasFailure:
return StatusFailure
case hasBlocked: case hasBlocked:
return StatusBlocked return StatusBlocked
default: default:

View File

@@ -80,31 +80,22 @@ type FindRunJobOptions struct {
func (opts FindRunJobOptions) ToConds() builder.Cond { func (opts FindRunJobOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.RunID > 0 { if opts.RunID > 0 {
cond = cond.And(builder.Eq{"`action_run_job`.run_id": opts.RunID}) cond = cond.And(builder.Eq{"run_id": opts.RunID})
} }
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"`action_run_job`.repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
} }
if opts.CommitSHA != "" { if opts.CommitSHA != "" {
cond = cond.And(builder.Eq{"`action_run_job`.commit_sha": opts.CommitSHA}) cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
} }
if len(opts.Statuses) > 0 { if len(opts.Statuses) > 0 {
cond = cond.And(builder.In("`action_run_job`.status", opts.Statuses)) cond = cond.And(builder.In("status", opts.Statuses))
} }
if opts.UpdatedBefore > 0 { if opts.UpdatedBefore > 0 {
cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore}) cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
} }
return cond return cond
} }
func (opts FindRunJobOptions) ToJoins() []db.JoinFunc {
if opts.OwnerID > 0 {
return []db.JoinFunc{
func(sess db.Engine) error {
sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
return nil
},
}
}
return nil
}

View File

@@ -58,14 +58,14 @@ func TestAggregateJobStatus(t *testing.T) {
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled}, {[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled}, {[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
// failure with other status, usually fail fast, but "running" wins to match GitHub's behavior // failure with other status, fail fast
// another reason that we can't make "failure" wins over "running": it would cause a weird behavior that user cannot cancel a workflow or get current running workflows correctly by filter after a job fail. // Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
{[]Status{StatusFailure}, StatusFailure}, {[]Status{StatusFailure}, StatusFailure},
{[]Status{StatusFailure, StatusSuccess}, StatusFailure}, {[]Status{StatusFailure, StatusSuccess}, StatusFailure},
{[]Status{StatusFailure, StatusSkipped}, StatusFailure}, {[]Status{StatusFailure, StatusSkipped}, StatusFailure},
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled}, {[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
{[]Status{StatusFailure, StatusWaiting}, StatusWaiting}, {[]Status{StatusFailure, StatusWaiting}, StatusFailure},
{[]Status{StatusFailure, StatusRunning}, StatusRunning}, {[]Status{StatusFailure, StatusRunning}, StatusFailure},
{[]Status{StatusFailure, StatusBlocked}, StatusFailure}, {[]Status{StatusFailure, StatusBlocked}, StatusFailure},
// skipped with other status // skipped with other status

View File

@@ -72,50 +72,39 @@ type FindRunOptions struct {
TriggerEvent webhook_module.HookEventType TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true Approved bool // not util.OptionalBool, it works only when it's true
Status []Status Status []Status
CommitSHA string
} }
func (opts FindRunOptions) ToConds() builder.Cond { func (opts FindRunOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"`action_run`.repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
} }
if opts.WorkflowID != "" { if opts.WorkflowID != "" {
cond = cond.And(builder.Eq{"`action_run`.workflow_id": opts.WorkflowID}) cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowID})
} }
if opts.TriggerUserID > 0 { if opts.TriggerUserID > 0 {
cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID}) cond = cond.And(builder.Eq{"trigger_user_id": opts.TriggerUserID})
} }
if opts.Approved { if opts.Approved {
cond = cond.And(builder.Gt{"`action_run`.approved_by": 0}) cond = cond.And(builder.Gt{"approved_by": 0})
} }
if len(opts.Status) > 0 { if len(opts.Status) > 0 {
cond = cond.And(builder.In("`action_run`.status", opts.Status)) cond = cond.And(builder.In("status", opts.Status))
} }
if opts.Ref != "" { if opts.Ref != "" {
cond = cond.And(builder.Eq{"`action_run`.ref": opts.Ref}) cond = cond.And(builder.Eq{"ref": opts.Ref})
} }
if opts.TriggerEvent != "" { if opts.TriggerEvent != "" {
cond = cond.And(builder.Eq{"`action_run`.trigger_event": opts.TriggerEvent}) cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
}
if opts.CommitSHA != "" {
cond = cond.And(builder.Eq{"`action_run`.commit_sha": opts.CommitSHA})
} }
return cond return cond
} }
func (opts FindRunOptions) ToJoins() []db.JoinFunc {
if opts.OwnerID > 0 {
return []db.JoinFunc{func(sess db.Engine) error {
sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
return nil
}}
}
return nil
}
func (opts FindRunOptions) ToOrders() string { func (opts FindRunOptions) ToOrders() string {
return "`action_run`.`id` DESC" return "`id` DESC"
} }
type StatusInfo struct { type StatusInfo struct {

View File

@@ -1,35 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestUpdateRepoRunsNumbers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// update the number to a wrong one, the original is 3
_, err := db.GetEngine(t.Context()).ID(4).Cols("num_closed_action_runs").Update(&repo_model.Repository{
NumClosedActionRuns: 2,
})
assert.NoError(t, err)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.Equal(t, 4, repo.NumActionRuns)
assert.Equal(t, 2, repo.NumClosedActionRuns)
// now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated
err = updateRepoRunsNumbers(t.Context(), repo)
assert.NoError(t, err)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.Equal(t, 5, repo.NumActionRuns)
assert.Equal(t, 3, repo.NumClosedActionRuns)
}

View File

@@ -5,7 +5,6 @@ package actions
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@@ -299,23 +298,6 @@ func DeleteRunner(ctx context.Context, id int64) error {
return err return err
} }
// DeleteEphemeralRunner deletes a ephemeral runner by given ID.
func DeleteEphemeralRunner(ctx context.Context, id int64) error {
runner, err := GetRunnerByID(ctx, id)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
return nil
}
return err
}
if !runner.Ephemeral {
return nil
}
_, err = db.DeleteByID[ActionRunner](ctx, id)
return err
}
// CreateRunner creates new runner. // CreateRunner creates new runner.
func CreateRunner(ctx context.Context, t *ActionRunner) error { func CreateRunner(ctx context.Context, t *ActionRunner) error {
if t.OwnerID != 0 && t.RepoID != 0 { if t.OwnerID != 0 && t.RepoID != 0 {

Some files were not shown because too many files have changed in this diff Show More