mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-19 05:50:54 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd4e10990 | ||
|
|
0a1df294c8 | ||
|
|
52a964d1fc | ||
|
|
d3dbe0d9ce | ||
|
|
cdbbdbef06 | ||
|
|
79f555d465 | ||
|
|
ae2b795693 | ||
|
|
d1fdbf46bd | ||
|
|
f27a75564a | ||
|
|
958d0db4f4 | ||
|
|
4c2441ba5d | ||
|
|
6f5f0be9e3 | ||
|
|
23d2d224c2 | ||
|
|
a43d829de8 | ||
|
|
8ab1363fef | ||
|
|
178fd90852 | ||
|
|
b39f7a37d1 | ||
|
|
b9ed8fceff | ||
|
|
e6ce72b14a | ||
|
|
2eecd58bbe | ||
|
|
64b9b21790 | ||
|
|
3290aff964 | ||
|
|
7ed1e8987e | ||
|
|
f10e909fce | ||
|
|
a3b25436f2 | ||
|
|
b947bc4363 | ||
|
|
18dc41d6f8 | ||
|
|
bf5d00074d | ||
|
|
fb4e9f92f9 | ||
|
|
468d1919b5 | ||
|
|
1b788946a7 | ||
|
|
e8646ad1d8 | ||
|
|
29dc9c784e | ||
|
|
b1cc4bf77f | ||
|
|
d35161ceb8 | ||
|
|
8defca6d39 | ||
|
|
fac434da0a | ||
|
|
e18eae7129 | ||
|
|
c60bc26fd3 | ||
|
|
bacc69db83 | ||
|
|
c5da032193 | ||
|
|
3ace45c118 | ||
|
|
5d6c5ce71a | ||
|
|
7baa6fa47c | ||
|
|
f9a0b077a7 | ||
|
|
d3317ebabe | ||
|
|
e9481e1da3 | ||
|
|
8965c068e9 | ||
|
|
eaaa158df3 | ||
|
|
f5498421c4 | ||
|
|
a6a14c9a92 | ||
|
|
d0ec1788b8 | ||
|
|
c1202f1b57 | ||
|
|
1162cbccc0 | ||
|
|
038990e0ff | ||
|
|
03ff09870d | ||
|
|
8bf4f2cc8f | ||
|
|
21731c1370 | ||
|
|
a0e272d95a | ||
|
|
47537a8361 | ||
|
|
d018c1b4b1 | ||
|
|
d2cbe2fba0 | ||
|
|
d6233c25b5 | ||
|
|
2bf2d00c8a | ||
|
|
9bd56a8ba0 | ||
|
|
a1dc3c9bd1 | ||
|
|
47ee84d1f3 | ||
|
|
89f1df033a | ||
|
|
94b67f1967 | ||
|
|
0a9a84df11 | ||
|
|
cdac263bb8 | ||
|
|
a5c7df7a4c | ||
|
|
6d738fecc4 | ||
|
|
38cc7453e2 | ||
|
|
b44175c071 | ||
|
|
947358dffe | ||
|
|
be1090cb2d | ||
|
|
c8f3402841 | ||
|
|
a3a95a0b67 | ||
|
|
ed527b664d | ||
|
|
e4717d426e | ||
|
|
16f15d2f7b | ||
|
|
b3f5196241 | ||
|
|
6c5f0af45d | ||
|
|
c95cb7c7e2 | ||
|
|
6747e3e0eb | ||
|
|
a12b5b3640 | ||
|
|
834dad8cef |
@@ -4,7 +4,7 @@ tmp_dir = ".air"
|
|||||||
[build]
|
[build]
|
||||||
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
|
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
|
||||||
cmd = "make --no-print-directory backend"
|
cmd = "make --no-print-directory backend"
|
||||||
entrypoint = ["./gitea"]
|
bin = "gitea"
|
||||||
delay = 2000
|
delay = 2000
|
||||||
include_ext = ["go", "tmpl"]
|
include_ext = ["go", "tmpl"]
|
||||||
include_file = ["main.go"]
|
include_file = ["main.go"]
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
{
|
{
|
||||||
"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-extra/features/poetry:2": {},
|
||||||
"ghcr.io/devcontainers/features/python:1": {
|
"ghcr.io/devcontainers/features/python:1": {
|
||||||
"version": "3.14"
|
"version": "3.12"
|
||||||
},
|
},
|
||||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"settings": {},
|
"settings": {},
|
||||||
|
// same extensions as Gitpod, should match /.gitpod.yml
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"editorconfig.editorconfig",
|
"editorconfig.editorconfig",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -74,9 +82,6 @@ cpu.out
|
|||||||
/VERSION
|
/VERSION
|
||||||
/.air
|
/.air
|
||||||
/.go-licenses
|
/.go-licenses
|
||||||
/Dockerfile
|
|
||||||
/Dockerfile.rootless
|
|
||||||
/.venv
|
|
||||||
|
|
||||||
# Files and folders that were previously generated
|
# Files and folders that were previously generated
|
||||||
/public/assets/img/webpack
|
/public/assets/img/webpack
|
||||||
|
|||||||
@@ -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
1004
.eslintrc.cjs
Normal file
File diff suppressed because it is too large
Load Diff
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -8,4 +8,3 @@
|
|||||||
/vendor/** -text -eol linguist-vendored
|
/vendor/** -text -eol linguist-vendored
|
||||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||||
Dockerfile.* linguist-language=Dockerfile
|
Dockerfile.* linguist-language=Dockerfile
|
||||||
Makefile.* linguist-language=Makefile
|
|
||||||
|
|||||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
labels: [modifies/dependencies]
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
cooldown:
|
|
||||||
default-days: 5
|
|
||||||
18
.github/labeler.yml
vendored
18
.github/labeler.yml
vendored
@@ -43,13 +43,15 @@ modifies/internal:
|
|||||||
- ".editorconfig"
|
- ".editorconfig"
|
||||||
- ".eslintrc.cjs"
|
- ".eslintrc.cjs"
|
||||||
- ".golangci.yml"
|
- ".golangci.yml"
|
||||||
|
- ".gitpod.yml"
|
||||||
- ".markdownlint.yaml"
|
- ".markdownlint.yaml"
|
||||||
- ".spectral.yaml"
|
- ".spectral.yaml"
|
||||||
- "stylelint.config.*"
|
- "stylelint.config.js"
|
||||||
- ".yamllint.yaml"
|
- ".yamllint.yaml"
|
||||||
- ".github/**"
|
- ".github/**"
|
||||||
- ".gitea/**"
|
- ".gitea/**"
|
||||||
- ".devcontainer/**"
|
- ".devcontainer/**"
|
||||||
|
- "build.go"
|
||||||
- "build/**"
|
- "build/**"
|
||||||
- "contrib/**"
|
- "contrib/**"
|
||||||
|
|
||||||
@@ -57,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"
|
||||||
|
|
||||||
@@ -79,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:
|
|
||||||
- ".golangci.yml"
|
|
||||||
- ".markdownlint.yaml"
|
|
||||||
- ".spectral.yaml"
|
|
||||||
- ".yamllint.yaml"
|
|
||||||
- "eslint*.config.*"
|
|
||||||
- "stylelint.config.*"
|
|
||||||
|
|||||||
22
.github/workflows/cron-flake-updater.yml
vendored
22
.github/workflows/cron-flake-updater.yml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: cron-flake-updater
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * 0' # runs weekly on Sunday at 00:00
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
nix-flake-update:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: DeterminateSystems/determinate-nix-action@v3
|
|
||||||
- uses: DeterminateSystems/update-flake-lock@main
|
|
||||||
with:
|
|
||||||
pr-title: "Update Nix flake"
|
|
||||||
pr-labels: |
|
|
||||||
dependencies
|
|
||||||
8
.github/workflows/cron-licenses.yml
vendored
8
.github/workflows/cron-licenses.yml
vendored
@@ -9,18 +9,16 @@ jobs:
|
|||||||
cron-licenses:
|
cron-licenses:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'go-gitea/gitea'
|
if: github.repository == 'go-gitea/gitea'
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
- run: make generate-gitignore
|
- run: make generate-gitignore
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
- name: push translations to repo
|
- name: push translations to repo
|
||||||
uses: appleboy/git-push-action@v1.2.0
|
uses: appleboy/git-push-action@v0.0.3
|
||||||
with:
|
with:
|
||||||
author_email: "teabot@gitea.io"
|
author_email: "teabot@gitea.io"
|
||||||
author_name: GiteaBot
|
author_name: GiteaBot
|
||||||
|
|||||||
8
.github/workflows/cron-translations.yml
vendored
8
.github/workflows/cron-translations.yml
vendored
@@ -9,11 +9,9 @@ jobs:
|
|||||||
crowdin-pull:
|
crowdin-pull:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'go-gitea/gitea'
|
if: github.repository == 'go-gitea/gitea'
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: crowdin/github-action@v2
|
- uses: crowdin/github-action@v1
|
||||||
with:
|
with:
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
@@ -29,7 +27,7 @@ jobs:
|
|||||||
- name: update locales
|
- name: update locales
|
||||||
run: ./build/update-locales.sh
|
run: ./build/update-locales.sh
|
||||||
- name: push translations to repo
|
- name: push translations to repo
|
||||||
uses: appleboy/git-push-action@v1.2.0
|
uses: appleboy/git-push-action@v0.0.3
|
||||||
with:
|
with:
|
||||||
author_email: "teabot@gitea.io"
|
author_email: "teabot@gitea.io"
|
||||||
author_name: GiteaBot
|
author_name: GiteaBot
|
||||||
|
|||||||
24
.github/workflows/files-changed.yml
vendored
24
.github/workflows/files-changed.yml
vendored
@@ -19,15 +19,11 @@ on:
|
|||||||
value: ${{ jobs.detect.outputs.swagger }}
|
value: ${{ jobs.detect.outputs.swagger }}
|
||||||
yaml:
|
yaml:
|
||||||
value: ${{ jobs.detect.outputs.yaml }}
|
value: ${{ jobs.detect.outputs.yaml }}
|
||||||
json:
|
|
||||||
value: ${{ jobs.detect.outputs.json }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
detect:
|
detect:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
outputs:
|
outputs:
|
||||||
backend: ${{ steps.changes.outputs.backend }}
|
backend: ${{ steps.changes.outputs.backend }}
|
||||||
frontend: ${{ steps.changes.outputs.frontend }}
|
frontend: ${{ steps.changes.outputs.frontend }}
|
||||||
@@ -37,10 +33,9 @@ jobs:
|
|||||||
docker: ${{ steps.changes.outputs.docker }}
|
docker: ${{ steps.changes.outputs.docker }}
|
||||||
swagger: ${{ steps.changes.outputs.swagger }}
|
swagger: ${{ steps.changes.outputs.swagger }}
|
||||||
yaml: ${{ steps.changes.outputs.yaml }}
|
yaml: ${{ steps.changes.outputs.yaml }}
|
||||||
json: ${{ steps.changes.outputs.json }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: dorny/paths-filter@v4
|
- uses: dorny/paths-filter@v3
|
||||||
id: changes
|
id: changes
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
@@ -53,7 +48,7 @@ jobs:
|
|||||||
- "Makefile"
|
- "Makefile"
|
||||||
- ".golangci.yml"
|
- ".golangci.yml"
|
||||||
- ".editorconfig"
|
- ".editorconfig"
|
||||||
- "options/locale/locale_en-US.json"
|
- "options/locale/locale_en-US.ini"
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
- "*.js"
|
- "*.js"
|
||||||
@@ -63,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"
|
||||||
@@ -72,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/*"
|
||||||
@@ -82,10 +77,9 @@ jobs:
|
|||||||
- "tools/lint-templates-*.js"
|
- "tools/lint-templates-*.js"
|
||||||
- "templates/**/*.tmpl"
|
- "templates/**/*.tmpl"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "uv.lock"
|
- "poetry.lock"
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
- ".github/workflows/pull-docker-dryrun.yml"
|
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
- "Dockerfile.rootless"
|
- "Dockerfile.rootless"
|
||||||
- "docker/**"
|
- "docker/**"
|
||||||
@@ -96,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:
|
||||||
@@ -104,6 +98,4 @@ jobs:
|
|||||||
- "**/*.yaml"
|
- "**/*.yaml"
|
||||||
- ".yamllint.yaml"
|
- ".yamllint.yaml"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
|
- "poetry.lock"
|
||||||
json:
|
|
||||||
- "**/*.json"
|
|
||||||
|
|||||||
129
.github/workflows/pull-compliance.yml
vendored
129
.github/workflows/pull-compliance.yml
vendored
@@ -10,18 +10,14 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
lint-backend:
|
lint-backend:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
@@ -34,18 +30,17 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.templates == 'true'
|
if: needs.files-changed.outputs.templates == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: astral-sh/setup-uv@v8.0.0
|
- uses: actions/setup-python@v5
|
||||||
- run: uv python install 3.14
|
|
||||||
- uses: pnpm/action-setup@v5
|
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
python-version: "3.12"
|
||||||
cache: pnpm
|
- uses: actions/setup-node@v4
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
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
|
||||||
@@ -54,44 +49,26 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.yaml == 'true'
|
if: needs.files-changed.outputs.yaml == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: astral-sh/setup-uv@v8.0.0
|
- uses: actions/setup-python@v5
|
||||||
- run: uv python install 3.14
|
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
|
||||||
|
|
||||||
lint-json:
|
|
||||||
if: needs.files-changed.outputs.json == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: pnpm/action-setup@v5
|
|
||||||
- uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make lint-json
|
|
||||||
|
|
||||||
lint-swagger:
|
lint-swagger:
|
||||||
if: needs.files-changed.outputs.swagger == 'true'
|
if: needs.files-changed.outputs.swagger == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: actions/setup-node@v4
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 22
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-swagger
|
- run: make lint-swagger
|
||||||
|
|
||||||
@@ -99,11 +76,9 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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,16 +88,14 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: make lint-go-windows
|
- run: make lint-go-windows lint-go-gitea-vet
|
||||||
env:
|
env:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
GOOS: windows
|
GOOS: windows
|
||||||
@@ -132,11 +105,9 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
@@ -149,11 +120,9 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
@@ -164,16 +133,13 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: actions/setup-node@v4
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 22
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
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
|
||||||
@@ -184,11 +150,9 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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,16 +182,13 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: actions/setup-node@v4
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 22
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
- run: make lint-md
|
- run: make lint-md
|
||||||
|
|
||||||
@@ -235,11 +196,9 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
|
|||||||
48
.github/workflows/pull-db-tests.yml
vendored
48
.github/workflows/pull-db-tests.yml
vendored
@@ -10,15 +10,11 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
test-pgsql:
|
test-pgsql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
services:
|
services:
|
||||||
pgsql:
|
pgsql:
|
||||||
image: postgres:14
|
image: postgres:14
|
||||||
@@ -35,15 +31,15 @@ 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
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000"
|
- "9000:9000"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
@@ -63,39 +59,37 @@ jobs:
|
|||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
TEST_TAGS: gogit
|
TEST_TAGS: gogit
|
||||||
TEST_LDAP: 1
|
TEST_LDAP: 1
|
||||||
|
USE_REPO_TEST_DIR: 1
|
||||||
|
|
||||||
test-sqlite:
|
test-sqlite:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
- run: GOEXPERIMENT='' make backend
|
- run: make backend
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-sqlite-migration
|
run: make test-sqlite-migration
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: GOEXPERIMENT='' make test-sqlite
|
run: make test-sqlite
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
TEST_TAGS: gogit sqlite sqlite_unlock_notify
|
TEST_TAGS: gogit sqlite sqlite_unlock_notify
|
||||||
|
USE_REPO_TEST_DIR: 1
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
services:
|
services:
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
image: elasticsearch:7.5.0
|
image: elasticsearch:7.5.0
|
||||||
@@ -119,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
|
||||||
@@ -130,8 +124,8 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 10000:10000
|
- 10000:10000
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
@@ -148,7 +142,7 @@ jobs:
|
|||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||||
- name: unit-tests-gogit
|
- name: unit-tests-gogit
|
||||||
run: GOEXPERIMENT='' make unit-test-coverage test-check
|
run: make unit-test-coverage test-check
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit
|
TAGS: bindata gogit
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
@@ -158,12 +152,10 @@ jobs:
|
|||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
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
|
||||||
@@ -185,8 +177,8 @@ jobs:
|
|||||||
- "587:587"
|
- "587:587"
|
||||||
- "993:993"
|
- "993:993"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
@@ -204,14 +196,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
|
USE_REPO_TEST_DIR: 1
|
||||||
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
||||||
|
|
||||||
test-mssql:
|
test-mssql:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
services:
|
services:
|
||||||
mssql:
|
mssql:
|
||||||
image: mcr.microsoft.com/mssql/server:2019-latest
|
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||||
@@ -226,8 +217,8 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 10000:10000
|
- 10000:10000
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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
|
||||||
@@ -243,3 +234,4 @@ jobs:
|
|||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
|
USE_REPO_TEST_DIR: 1
|
||||||
|
|||||||
33
.github/workflows/pull-docker-dryrun.yml
vendored
33
.github/workflows/pull-docker-dryrun.yml
vendored
@@ -10,31 +10,26 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
container:
|
regular:
|
||||||
if: needs.files-changed.outputs.docker == 'true'
|
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: docker/setup-buildx-action@v3
|
||||||
- uses: docker/setup-qemu-action@v4
|
- uses: docker/build-push-action@v5
|
||||||
- uses: docker/setup-buildx-action@v4
|
|
||||||
- name: Build regular container image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
with:
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: false
|
push: false
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
tags: gitea/gitea:linux-amd64
|
||||||
- name: Build rootless container image
|
|
||||||
uses: docker/build-push-action@v7
|
rootless:
|
||||||
|
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
|
needs: files-changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
|
||||||
push: false
|
push: false
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
|
tags: gitea/gitea:linux-amd64
|
||||||
|
|||||||
35
.github/workflows/pull-e2e-tests.yml
vendored
35
.github/workflows/pull-e2e-tests.yml
vendored
@@ -10,34 +10,27 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
files-changed:
|
files-changed:
|
||||||
uses: ./.github/workflows/files-changed.yml
|
uses: ./.github/workflows/files-changed.yml
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true'
|
# the "test-e2e" won't pass, and it seems that there is no useful test, so skip
|
||||||
|
# if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
|
if: false
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- 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@v5
|
- uses: actions/setup-node@v4
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 22
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend frontend deps-backend
|
||||||
- run: make frontend
|
- run: npx playwright install --with-deps
|
||||||
- run: make deps-backend
|
- run: make test-e2e-sqlite
|
||||||
- run: make gitea-e2e
|
timeout-minutes: 40
|
||||||
- run: make playwright
|
|
||||||
- run: make test-e2e
|
|
||||||
timeout-minutes: 10
|
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: 1
|
USE_REPO_TEST_DIR: 1
|
||||||
GITEA_TEST_E2E_DEBUG: 1
|
|
||||||
|
|||||||
2
.github/workflows/pull-labeler.yml
vendored
2
.github/workflows/pull-labeler.yml
vendored
@@ -15,6 +15,6 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v6
|
- uses: actions/labeler@v5
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
|||||||
127
.github/workflows/release-nightly.yml
vendored
127
.github/workflows/release-nightly.yml
vendored
@@ -11,23 +11,20 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
nightly-binary:
|
nightly-binary:
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
runs-on: namespace-profile-gitea-release-binary
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all 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@v5
|
- uses: actions/setup-node@v4
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 22
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
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
|
||||||
@@ -35,7 +32,7 @@ jobs:
|
|||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
- name: import gpg key
|
- name: import gpg key
|
||||||
id: import_gpg
|
id: import_gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v7
|
uses: crazy-max/ghaction-import-gpg@v6
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||||
@@ -52,7 +49,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -60,76 +57,98 @@ jobs:
|
|||||||
- name: upload binaries to s3
|
- name: upload binaries to s3
|
||||||
run: |
|
run: |
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
|
nightly-docker-rootful:
|
||||||
nightly-container:
|
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all 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: docker/setup-qemu-action@v4
|
- uses: actions/setup-go@v5
|
||||||
- uses: docker/setup-buildx-action@v4
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
- uses: docker/setup-qemu-action@v3
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
- 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"
|
||||||
- uses: docker/metadata-action@v6
|
|
||||||
id: meta
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
tags: |
|
|
||||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- uses: docker/metadata-action@v6
|
|
||||||
id: meta_rootless
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
# each tag below will have the suffix of -rootless
|
|
||||||
flavor: |
|
|
||||||
suffix=-rootless
|
|
||||||
tags: |
|
|
||||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular docker image
|
- name: fetch go modules
|
||||||
uses: docker/build-push-action@v7
|
run: make vendor
|
||||||
|
- name: build rootful docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: |-
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
|
nightly-docker-rootless:
|
||||||
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
- uses: docker/setup-qemu-action@v3
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Get cleaned branch name
|
||||||
|
id: clean_name
|
||||||
|
run: |
|
||||||
|
# if main then say nightly otherwise cleanup name
|
||||||
|
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||||
|
echo "branch=nightly" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: fetch go modules
|
||||||
|
run: make vendor
|
||||||
- name: build rootless docker image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
tags: |-
|
||||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
|
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless,mode=max
|
|
||||||
|
|||||||
91
.github/workflows/release-tag-rc.yml
vendored
91
.github/workflows/release-tag-rc.yml
vendored
@@ -12,23 +12,20 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
binary:
|
binary:
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
runs-on: namespace-profile-gitea-release-binary
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all 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@v5
|
- uses: actions/setup-node@v4
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 22
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
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
|
||||||
@@ -36,7 +33,7 @@ jobs:
|
|||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
- name: import gpg key
|
- name: import gpg key
|
||||||
id: import_gpg
|
id: import_gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v7
|
uses: crazy-max/ghaction-import-gpg@v6
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||||
@@ -53,7 +50,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -62,7 +59,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
- name: Install GH CLI
|
- name: Install GH CLI
|
||||||
uses: dev-hanz-ops/install-gh-cli-action@v0.2.1
|
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
|
||||||
with:
|
with:
|
||||||
gh-cli-version: 2.39.1
|
gh-cli-version: 2.39.1
|
||||||
- name: create github release
|
- name: create github release
|
||||||
@@ -70,20 +67,18 @@ jobs:
|
|||||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
|
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
docker-rootful:
|
||||||
container:
|
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all 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: docker/setup-qemu-action@v4
|
- uses: docker/setup-qemu-action@v3
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@v3
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -94,10 +89,38 @@ jobs:
|
|||||||
# 1.2.3-rc0
|
# 1.2.3-rc0
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
annotations: |
|
- name: Login to Docker Hub
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
uses: docker/login-action@v3
|
||||||
- uses: docker/metadata-action@v6
|
with:
|
||||||
id: meta_rootless
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: build rootful docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
docker-rootless:
|
||||||
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
permissions:
|
||||||
|
packages: write # to publish to ghcr.io
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
|
- uses: docker/setup-qemu-action@v3
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/metadata-action@v5
|
||||||
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
gitea/gitea
|
gitea/gitea
|
||||||
@@ -109,33 +132,23 @@ jobs:
|
|||||||
# 1.2.3-rc0
|
# 1.2.3-rc0
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular container image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v5
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
|
||||||
- name: build rootless container image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
88
.github/workflows/release-tag-version.yml
vendored
88
.github/workflows/release-tag-version.yml
vendored
@@ -15,23 +15,21 @@ jobs:
|
|||||||
binary:
|
binary:
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
runs-on: namespace-profile-gitea-release-binary
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all 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@v5
|
- uses: actions/setup-node@v4
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 22
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
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
|
||||||
@@ -39,7 +37,7 @@ jobs:
|
|||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
- name: import gpg key
|
- name: import gpg key
|
||||||
id: import_gpg
|
id: import_gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v7
|
uses: crazy-max/ghaction-import-gpg@v6
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||||
@@ -56,7 +54,7 @@ jobs:
|
|||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -65,7 +63,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||||
- name: Install GH CLI
|
- name: Install GH CLI
|
||||||
uses: dev-hanz-ops/install-gh-cli-action@v0.2.1
|
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
|
||||||
with:
|
with:
|
||||||
gh-cli-version: 2.39.1
|
gh-cli-version: 2.39.1
|
||||||
- name: create github release
|
- name: create github release
|
||||||
@@ -73,20 +71,18 @@ jobs:
|
|||||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
|
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
docker-rootful:
|
||||||
container:
|
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
packages: write # to publish to ghcr.io
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
# fetch all 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: docker/setup-qemu-action@v4
|
- uses: docker/setup-qemu-action@v3
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@v3
|
||||||
- uses: docker/metadata-action@v6
|
- uses: docker/metadata-action@v5
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
@@ -101,10 +97,36 @@ jobs:
|
|||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
annotations: |
|
- name: Login to Docker Hub
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
uses: docker/login-action@v3
|
||||||
- uses: docker/metadata-action@v6
|
with:
|
||||||
id: meta_rootless
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to GHCR using PAT
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: build rootful docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
docker-rootless:
|
||||||
|
runs-on: namespace-profile-gitea-release-docker
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||||
|
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||||
|
- run: git fetch --unshallow --quiet --tags --force
|
||||||
|
- uses: docker/setup-qemu-action@v3
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/metadata-action@v5
|
||||||
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: |-
|
images: |-
|
||||||
gitea/gitea
|
gitea/gitea
|
||||||
@@ -121,33 +143,23 @@ jobs:
|
|||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to GHCR using PAT
|
- name: Login to GHCR using PAT
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: build regular container image
|
- name: build rootless docker image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v5
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
|
||||||
- name: build rootless container image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -22,12 +22,6 @@ _test
|
|||||||
.vscode
|
.vscode
|
||||||
__debug_bin*
|
__debug_bin*
|
||||||
|
|
||||||
# Visual Studio
|
|
||||||
/.vs/
|
|
||||||
|
|
||||||
# mise version managment tool
|
|
||||||
mise.toml
|
|
||||||
|
|
||||||
*.cgo1.go
|
*.cgo1.go
|
||||||
*.cgo2.c
|
*.cgo2.c
|
||||||
_cgo_defun.c
|
_cgo_defun.c
|
||||||
@@ -55,7 +49,6 @@ cpu.out
|
|||||||
*.log.*.gz
|
*.log.*.gz
|
||||||
|
|
||||||
/gitea
|
/gitea
|
||||||
/gitea-e2e
|
|
||||||
/gitea-vet
|
/gitea-vet
|
||||||
/debug
|
/debug
|
||||||
/integrations.test
|
/integrations.test
|
||||||
@@ -68,9 +61,13 @@ cpu.out
|
|||||||
/indexers
|
/indexers
|
||||||
/log
|
/log
|
||||||
/public/assets/img/avatar
|
/public/assets/img/avatar
|
||||||
/tests/e2e-output
|
|
||||||
/tests/integration/gitea-integration-*
|
/tests/integration/gitea-integration-*
|
||||||
/tests/integration/indexers-*
|
/tests/integration/indexers-*
|
||||||
|
/tests/e2e/gitea-e2e-*
|
||||||
|
/tests/e2e/indexers-*
|
||||||
|
/tests/e2e/reports
|
||||||
|
/tests/e2e/test-artifacts
|
||||||
|
/tests/e2e/test-snapshots
|
||||||
/tests/*.ini
|
/tests/*.ini
|
||||||
/tests/**/*.git/**/*.sample
|
/tests/**/*.git/**/*.sample
|
||||||
/node_modules
|
/node_modules
|
||||||
@@ -78,8 +75,6 @@ cpu.out
|
|||||||
/yarn.lock
|
/yarn.lock
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/npm-debug.log*
|
/npm-debug.log*
|
||||||
/.pnpm-store
|
|
||||||
/public/assets/.vite
|
|
||||||
/public/assets/js
|
/public/assets/js
|
||||||
/public/assets/css
|
/public/assets/css
|
||||||
/public/assets/fonts
|
/public/assets/fonts
|
||||||
@@ -87,7 +82,10 @@ cpu.out
|
|||||||
/vendor
|
/vendor
|
||||||
/VERSION
|
/VERSION
|
||||||
/.air
|
/.air
|
||||||
|
/.go-licenses
|
||||||
|
|
||||||
|
# Files and folders that were previously generated
|
||||||
|
/public/assets/img/webpack
|
||||||
|
|
||||||
# Snapcraft
|
# Snapcraft
|
||||||
/gitea_a*.txt
|
/gitea_a*.txt
|
||||||
@@ -108,18 +106,3 @@ prime/
|
|||||||
|
|
||||||
# Manpage
|
# Manpage
|
||||||
/man
|
/man
|
||||||
|
|
||||||
# Ignore AI/LLM instruction files
|
|
||||||
/.claude/
|
|
||||||
/.cursorrules
|
|
||||||
/.cursor/
|
|
||||||
/.goosehints
|
|
||||||
/.windsurfrules
|
|
||||||
/.github/copilot-instructions.md
|
|
||||||
/llms.txt
|
|
||||||
|
|
||||||
# Ignore worktrees when working on multiple branches
|
|
||||||
.worktrees/
|
|
||||||
|
|
||||||
# A Makefile for custom make targets
|
|
||||||
Makefile.local
|
|
||||||
|
|||||||
51
.gitpod.yml
Normal file
51
.gitpod.yml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
tasks:
|
||||||
|
- name: Setup
|
||||||
|
init: |
|
||||||
|
cp -r contrib/ide/vscode .vscode
|
||||||
|
make deps
|
||||||
|
make build
|
||||||
|
command: |
|
||||||
|
gp sync-done setup
|
||||||
|
exit 0
|
||||||
|
- name: Run backend
|
||||||
|
command: |
|
||||||
|
gp sync-await setup
|
||||||
|
|
||||||
|
# Get the URL and extract the domain
|
||||||
|
url=$(gp url 3000)
|
||||||
|
domain=$(echo $url | awk -F[/:] '{print $4}')
|
||||||
|
|
||||||
|
if [ -f custom/conf/app.ini ]; then
|
||||||
|
sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini
|
||||||
|
sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini
|
||||||
|
sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini
|
||||||
|
sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini
|
||||||
|
else
|
||||||
|
mkdir -p custom/conf/
|
||||||
|
echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini
|
||||||
|
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
|
||||||
|
fi
|
||||||
|
export TAGS="sqlite sqlite_unlock_notify"
|
||||||
|
make watch-backend
|
||||||
|
- name: Run frontend
|
||||||
|
command: |
|
||||||
|
gp sync-await setup
|
||||||
|
make watch-frontend
|
||||||
|
openMode: split-right
|
||||||
|
|
||||||
|
vscode:
|
||||||
|
extensions:
|
||||||
|
- editorconfig.editorconfig
|
||||||
|
- dbaeumer.vscode-eslint
|
||||||
|
- golang.go
|
||||||
|
- stylelint.vscode-stylelint
|
||||||
|
- DavidAnson.vscode-markdownlint
|
||||||
|
- Vue.volar
|
||||||
|
- ms-azuretools.vscode-docker
|
||||||
|
- vitest.explorer
|
||||||
|
- cweijan.vscode-database-client2
|
||||||
|
- GitHub.vscode-pull-request-github
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- name: Gitea
|
||||||
|
port: 3000
|
||||||
@@ -6,20 +6,15 @@ linters:
|
|||||||
default: none
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- bidichk
|
- bidichk
|
||||||
- bodyclose
|
|
||||||
- depguard
|
- depguard
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- errcheck
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gocritic
|
- gocritic
|
||||||
- goheader
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- mirror
|
- mirror
|
||||||
- modernize
|
|
||||||
- nakedret
|
- nakedret
|
||||||
- nilnil
|
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- perfsprint
|
- perfsprint
|
||||||
- revive
|
- revive
|
||||||
@@ -50,54 +45,44 @@ 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
|
||||||
- pkg: github.com/pkg/errors
|
|
||||||
desc: use builtin errors package instead
|
|
||||||
migrations:
|
|
||||||
files:
|
|
||||||
- '**/models/migrations/**/*.go'
|
|
||||||
deny:
|
|
||||||
- pkg: code.gitea.io/gitea/models$
|
|
||||||
desc: migrations must not depend on the models package
|
|
||||||
- pkg: code.gitea.io/gitea/modules/structs
|
|
||||||
desc: migrations must not depend on modules/structs (API structures change over time)
|
|
||||||
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.
|
||||||
- deprecatedComment # conflicts with go-swagger comments
|
|
||||||
revive:
|
revive:
|
||||||
severity: error
|
severity: error
|
||||||
rules:
|
rules:
|
||||||
|
- name: atomic
|
||||||
|
- name: bare-return
|
||||||
- name: blank-imports
|
- name: blank-imports
|
||||||
- name: constant-logical-expr
|
- name: constant-logical-expr
|
||||||
- name: context-as-argument
|
- name: context-as-argument
|
||||||
- name: context-keys-type
|
- name: context-keys-type
|
||||||
- name: dot-imports
|
- name: dot-imports
|
||||||
|
- name: duplicated-imports
|
||||||
- name: empty-lines
|
- name: empty-lines
|
||||||
|
- name: error-naming
|
||||||
- name: error-return
|
- name: error-return
|
||||||
- name: error-strings
|
- name: error-strings
|
||||||
|
- name: errorf
|
||||||
- name: exported
|
- name: exported
|
||||||
- name: identical-branches
|
- name: identical-branches
|
||||||
- name: if-return
|
- name: if-return
|
||||||
- name: increment-decrement
|
- name: increment-decrement
|
||||||
|
- name: indent-error-flow
|
||||||
- name: modifies-value-receiver
|
- name: modifies-value-receiver
|
||||||
- name: package-comments
|
- name: package-comments
|
||||||
|
- name: range
|
||||||
|
- name: receiver-naming
|
||||||
- name: redefines-builtin-id
|
- name: redefines-builtin-id
|
||||||
|
- name: string-of-int
|
||||||
- name: superfluous-else
|
- name: superfluous-else
|
||||||
- name: time-naming
|
- name: time-naming
|
||||||
|
- name: unconditional-recursion
|
||||||
- name: unexported-return
|
- name: unexported-return
|
||||||
|
- 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
|
||||||
@@ -112,17 +97,6 @@ linters:
|
|||||||
- require-error
|
- require-error
|
||||||
usetesting:
|
usetesting:
|
||||||
os-temp-dir: true
|
os-temp-dir: true
|
||||||
perfsprint:
|
|
||||||
concat-loop: false
|
|
||||||
govet:
|
|
||||||
enable:
|
|
||||||
- nilness
|
|
||||||
- unusedwrite
|
|
||||||
goheader:
|
|
||||||
values:
|
|
||||||
regexp:
|
|
||||||
HEADER: '((Copyright [^\n]+|All rights reserved\.)\n)*Copyright \d{4} (The (Gogs|Gitea) Authors|Gitea Authors|Gitea)\.( All rights reserved\.)?(\n(Copyright [^\n]+|All rights reserved\.))*\nSPDX-License-Identifier: [\w.-]+'
|
|
||||||
template: '{{ HEADER }}'
|
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
generated: lax
|
||||||
presets:
|
presets:
|
||||||
@@ -134,12 +108,16 @@ linters:
|
|||||||
- linters:
|
- linters:
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- errcheck
|
||||||
|
- gocyclo
|
||||||
|
- gosec
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- unparam
|
- unparam
|
||||||
path: _test\.go
|
path: _test\.go
|
||||||
- linters:
|
- linters:
|
||||||
- dupl
|
- dupl
|
||||||
- errcheck
|
- errcheck
|
||||||
|
- gocyclo
|
||||||
|
- gosec
|
||||||
path: models/migrations/v
|
path: models/migrations/v
|
||||||
- linters:
|
- linters:
|
||||||
- forbidigo
|
- forbidigo
|
||||||
@@ -151,8 +129,12 @@ linters:
|
|||||||
- gocritic
|
- gocritic
|
||||||
text: (?i)`ID' should not be capitalized
|
text: (?i)`ID' should not be capitalized
|
||||||
- linters:
|
- linters:
|
||||||
|
- deadcode
|
||||||
- unused
|
- unused
|
||||||
text: (?i)swagger
|
text: (?i)swagger
|
||||||
|
- linters:
|
||||||
|
- staticcheck
|
||||||
|
text: (?i)argument x is overwritten before first use
|
||||||
- linters:
|
- linters:
|
||||||
- gocritic
|
- gocritic
|
||||||
text: '(?i)commentFormatting: put a space between `//` and comment text'
|
text: '(?i)commentFormatting: put a space between `//` and comment text'
|
||||||
@@ -161,7 +143,6 @@ linters:
|
|||||||
text: '(?i)exitAfterDefer:'
|
text: '(?i)exitAfterDefer:'
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
- .venv
|
|
||||||
- public
|
- public
|
||||||
- web_src
|
- web_src
|
||||||
- third_party$
|
- third_party$
|
||||||
@@ -172,25 +153,20 @@ issues:
|
|||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
formatters:
|
formatters:
|
||||||
enable:
|
enable:
|
||||||
- gci
|
- gofmt
|
||||||
- gofumpt
|
- gofumpt
|
||||||
settings:
|
settings:
|
||||||
gci:
|
|
||||||
custom-order: true
|
|
||||||
sections:
|
|
||||||
- standard
|
|
||||||
- prefix(code.gitea.io/gitea)
|
|
||||||
- blank
|
|
||||||
- default
|
|
||||||
gofumpt:
|
gofumpt:
|
||||||
extra-rules: true
|
extra-rules: true
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
generated: lax
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
- .venv
|
|
||||||
- public
|
- public
|
||||||
- web_src
|
- web_src
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|
||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
|
|||||||
3
.ignore
3
.ignore
@@ -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
5
.npmrc
@@ -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
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ rules:
|
|||||||
comments-indentation:
|
comments-indentation:
|
||||||
level: error
|
level: error
|
||||||
|
|
||||||
document-start: disable
|
document-start:
|
||||||
|
level: error
|
||||||
|
present: false
|
||||||
|
|
||||||
document-end:
|
document-end:
|
||||||
present: false
|
present: false
|
||||||
|
|||||||
11
AGENTS.md
11
AGENTS.md
@@ -1,11 +0,0 @@
|
|||||||
- Use `make help` to find available development targets
|
|
||||||
- Run `make fmt` to format `.go` files, and run `make lint-go` to lint them
|
|
||||||
- Run `make lint-js` to lint `.ts` files
|
|
||||||
- Run `make tidy` after any `go.mod` changes
|
|
||||||
- Add the current year into the copyright header of new `.go` files
|
|
||||||
- Ensure no trailing whitespace in edited files
|
|
||||||
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
|
|
||||||
- Preserve existing code comments, do not remove or rewrite comments that are still relevant
|
|
||||||
- In TypeScript, use `!` (non-null assertion) instead of `?.`/`??` when a value is known to always exist
|
|
||||||
- Include authorship attribution in issue and pull request comments
|
|
||||||
- Add `Co-Authored-By` lines to all commits, indicating name and model used
|
|
||||||
885
CHANGELOG.md
885
CHANGELOG.md
@@ -4,872 +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.26.0](https://github.com/go-gitea/gitea/releases/tag/v1.26.0) - 2026-04-17
|
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/1.24.2) - 2025-06-20
|
||||||
|
|
||||||
* BREAKING
|
|
||||||
* Correct swagger annotations for enums, status codes, and notification state (#37030)
|
|
||||||
* Remove GET API registration-token (#36801)
|
|
||||||
* Support Actions `concurrency` syntax (#32751)
|
|
||||||
* Make PUBLIC_URL_DETECTION default to "auto" (#36955)
|
|
||||||
* SECURITY
|
|
||||||
* Bound PageSize in `ListUnadoptedRepositories` (#36884)
|
|
||||||
* FEATURES
|
|
||||||
* Support Actions `concurrency` syntax (#32751)
|
|
||||||
* Add terraform state registry (#36710)
|
|
||||||
* Instance-wide (global) info banner and maintenance mode (#36571)
|
|
||||||
* Support rendering OpenAPI spec (#36449)
|
|
||||||
* Add keyboard shortcuts for repository file and code search (#36416)
|
|
||||||
* Add support for archive-upload rpc (#36391)
|
|
||||||
* Add ability to download subpath archive (#36371)
|
|
||||||
* Add workflow dependencies visualization (#26062) (#36248) & Restyle Workflow Graph (#36912)
|
|
||||||
* Automatic generation of release notes (#35977)
|
|
||||||
* Add "Go to file", "Delete Directory" to repo file list page (#35911)
|
|
||||||
* Introduce "config edit-ini" sub command to help maintaining INI config file (#35735)
|
|
||||||
* Add button to re-run failed jobs in Actions (#36924)
|
|
||||||
* Support actions and reusable workflows from private repos (#32562)
|
|
||||||
* Add summary to action runs view (#36883)
|
|
||||||
* Add user badges (#36752)
|
|
||||||
* Add configurable permissions for Actions automatic tokens (#36173)
|
|
||||||
* Add per-runner "Disable/Pause" (#36776)
|
|
||||||
* Feature non-zipped actions artifacts (action v7 / nodejs / npm v6.2.0) (#36786)
|
|
||||||
* PERFORMANCE
|
|
||||||
* WorkflowDispatch API optionally return runid (#36706)
|
|
||||||
* Add render cache for SVG icons (#36863)
|
|
||||||
* Load `mentionValues` asynchronously (#36739)
|
|
||||||
* Lazy-load some Vue components, fix heatmap chunk loading on every page (#36719)
|
|
||||||
* Load heatmap data asynchronously (#36622)
|
|
||||||
* Use prev/next pagination for user profile activities page to speed up (#36642)
|
|
||||||
* Refactor cat-file batch operations and support `--batch-command` approach (#35775)
|
|
||||||
* Use merge tree to detect conflicts when possible (#36400)
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Implement logout redirection for reverse proxy auth setups (#36085) (#37171)
|
|
||||||
* Adds option to force update new branch in contents routes (#35592)
|
|
||||||
* Add viewer controller for mermaid (zoom, drag) (#36557)
|
|
||||||
* Add code editor setting dropdowns (#36534)
|
|
||||||
* Add `elk` layout support to mermaid (#36486)
|
|
||||||
* Add resolve/unresolve review comment API endpoints (#36441)
|
|
||||||
* Allow configuring default PR base branch (fixes #36412) (#36425)
|
|
||||||
* Add support for RPM Errata (updateinfo.xml) (#37125)
|
|
||||||
* Require additional user confirmation for making repo private (#36959)
|
|
||||||
* Add `actions.WORKFLOW_DIRS` setting (#36619)
|
|
||||||
* Avoid opening new tab when downloading actions logs (#36740)
|
|
||||||
* Implements OIDC RP-Initiated Logout (#36724)
|
|
||||||
* Show workflow link (#37070)
|
|
||||||
* Desaturate dark theme background colors (#37056)
|
|
||||||
* Refactor "org teams" page and help new users to "add member" to an org (#37051)
|
|
||||||
* Add webhook name field to improve webhook identification (#37025) (#37040)
|
|
||||||
* Make task list checkboxes clickable in the preview tab (#37010)
|
|
||||||
* Improve severity labels in Actions logs and tweak colors (#36993)
|
|
||||||
* Linkify URLs in Actions workflow logs (#36986)
|
|
||||||
* Allow text selection on checkbox labels (#36970)
|
|
||||||
* Support dark/light theme images in markdown (#36922)
|
|
||||||
* Enable native dark mode for swagger-ui (#36899)
|
|
||||||
* Rework checkbox styling, remove `input` border hover effect (#36870)
|
|
||||||
* Refactor storage content-type handling of ServeDirectURL (#36804)
|
|
||||||
* Use "Enable Gravatar" but not "Disable" (#36771)
|
|
||||||
* Use case-insensitive matching for Git error "Not a valid object name" (#36728)
|
|
||||||
* Add "Copy Source" to markup comment menu (#36726)
|
|
||||||
* Change image transparency grid to CSS (#36711)
|
|
||||||
* Add "Run" prefix for unnamed action steps (#36624)
|
|
||||||
* Persist actions log time display settings in `localStorage` (#36623)
|
|
||||||
* Use first commit title for multi-commit PRs and fix auto-focus title field (#36606)
|
|
||||||
* Improve BuildCaseInsensitiveLike with lowercase (#36598)
|
|
||||||
* Improve diff highlighting (#36583)
|
|
||||||
* Exclude cancelled runs from failure-only email notifications (#36569)
|
|
||||||
* Use full-file highlighting for diff sections (#36561)
|
|
||||||
* Color command/error logs in Actions log (#36538)
|
|
||||||
* Add paging headers (#36521)
|
|
||||||
* Improve timeline entries for WIP prefix changes in pull requests (#36518)
|
|
||||||
* Add FOLDER_ICON_THEME configuration option (#36496)
|
|
||||||
* Normalize guessed languages for code highlighting (#36450)
|
|
||||||
* Add chunked transfer encoding support for LFS uploads (#36380)
|
|
||||||
* Indicate when only optional checks failed (#36367)
|
|
||||||
* Add 'allow_maintainer_edit' API option for creating a pull request (#36283)
|
|
||||||
* Support closing keywords with URL references (#36221)
|
|
||||||
* Improve diff file headers (#36215)
|
|
||||||
* Fix and enhance comment editor monospace toggle (#36181)
|
|
||||||
* Add git.DIFF_RENAME_SIMILARITY_THRESHOLD option (#36164)
|
|
||||||
* Add matching pair insertion to markdown textarea (#36121)
|
|
||||||
* Add sorting/filtering to admin user search API endpoint (#36112)
|
|
||||||
* Allow action user have read permission in public repo like other user (#36095)
|
|
||||||
* Disable matchBrackets in monaco (#36089)
|
|
||||||
* Use GitHub-style commit message for squash merge (#35987)
|
|
||||||
* Make composer registry support tar.gz and tar.bz2 and fix bugs (#35958)
|
|
||||||
* Add GITEA_PR_INDEX env variable to githooks (#35938)
|
|
||||||
* Add proper error message if session provider can not be created (#35520)
|
|
||||||
* Add button to copy file name in PR files (#35509)
|
|
||||||
* Move `X_FRAME_OPTIONS` setting from `cors` to `security` section (#30256)
|
|
||||||
* Add placeholder content for empty content page (#37114)
|
|
||||||
* Add `DEFAULT_DELETE_BRANCH_AFTER_MERGE` setting (#36917)
|
|
||||||
* Redirect to the only OAuth2 provider when no other login methods and fix various problems (#36901)
|
|
||||||
* Add admin badge to navbar avatar (#36790)
|
|
||||||
* Add `never` option to `PUBLIC_URL_DETECTION` configuration (#36785)
|
|
||||||
* Add background and run count to actions list page (#36707)
|
|
||||||
* Add icon to buttons "Close with Comment", "Close Pull Request", "Close Issue" (#36654)
|
|
||||||
* Add support for in_progress event in workflow_run webhook (#36979)
|
|
||||||
* Report commit status for pull_request_review events (#36589)
|
|
||||||
* Render merged pull request title as such in dashboard feed (#36479)
|
|
||||||
* Feature to be able to filter project boards by milestones (#36321)
|
|
||||||
* Use user id in noreply emails (#36550)
|
|
||||||
* Enable pagination on GiteaDownloader.getIssueReactions() (#36549)
|
|
||||||
* Remove striped tables in UI (#36509)
|
|
||||||
* Improve control char rendering and escape button styling (#37094)
|
|
||||||
* Support legacy run/job index-based URLs and refactor migration 326 (#37008)
|
|
||||||
* Add date to "No Contributions" tooltip (#36190)
|
|
||||||
* Show edit page confirmation dialog on tree view file change (#36130)
|
|
||||||
* Mention proc-receive in text for dashboard.resync_all_hooks func (#35991)
|
|
||||||
* Reuse selectable style for wiki (#35990)
|
|
||||||
* Support blue yellow colorblind theme (#35910)
|
|
||||||
* Support selecting theme on the footer (#35741)
|
|
||||||
* Improve online runner check (#35722)
|
|
||||||
* Add quick approve button on PR page (#35678)
|
|
||||||
* Enable commenting on expanded lines in PR diffs (#35662)
|
|
||||||
* Print PR-Title into tooltip for actions (#35579)
|
|
||||||
* Use explicit, stronger defaults for newly generated repo signing keys for Debian (#36236)
|
|
||||||
* Improve the compare page (#36261)
|
|
||||||
* Unify repo names in system notices (#36491)
|
|
||||||
* Move package settings to package instead of being tied to version (#37026)
|
|
||||||
* Add Actions API rerun endpoints for runs and jobs (#36768)
|
|
||||||
* Add branch_count to repository API (#35351) (#36743)
|
|
||||||
* Add created_by filter to SearchIssues (#36670)
|
|
||||||
* Allow admins to rename non-local users (#35970)
|
|
||||||
* Support updating branch via API (#35951)
|
|
||||||
* Add an option to automatically verify SSH keys from LDAP (#35927)
|
|
||||||
* Make "update file" API can create a new file when SHA is not set (#35738)
|
|
||||||
* Update issue.go with labels documentation (labels content, not ids) (#35522)
|
|
||||||
* Expose content_version for optimistic locking on issue and PR edits (#37035)
|
|
||||||
* Pass ServeHeaderOptions by value instead of pointer, fine tune httplib tests (#36982)
|
|
||||||
* BUGFIXES
|
|
||||||
* Frontend iframe renderer framework: 3D models, OpenAPI (#37233) (#37273)
|
|
||||||
* Fix CODEOWNERS absolute path matching. (#37244) (#37264)
|
|
||||||
* Swift registry metadata: preserve more JSON fields and accept empty metadata (#37254) (#37261)
|
|
||||||
* Fix user ssh key exporting and tests (#37256) (#37258)
|
|
||||||
* Fix team member avatar size and add tooltip (#37253)
|
|
||||||
* Fix commit title rendering in action run and blame (#37243) (#37251)
|
|
||||||
* Fix corrupted JSON caused by goccy library (#37214) (#37220)
|
|
||||||
* Add test for "fetch redirect", add CSS value validation for external render (#37207) (#37216)
|
|
||||||
* Fix incorrect concurrency check (#37205) (#37215)
|
|
||||||
* Fix handle missing base branch in PR commits API (#37193) (#37203)
|
|
||||||
* Fix encoding for Matrix Webhooks (#37190) (#37201)
|
|
||||||
* Fix handle fork-only commits in compare API (#37185) (#37199)
|
|
||||||
* Indicate form field readonly via background, fix RunUser config (#37175, #37180) (#37178)
|
|
||||||
* Report structurally invalid workflows to users (#37116) (#37164)
|
|
||||||
* Fix API not persisting pull request unit config when has_pull_requests is not set (#36718)
|
|
||||||
* Rename CSS variables and improve colorblind themes (#36353)
|
|
||||||
* Hide `add-matcher` and `remove-matcher` from actions job logs (#36520)
|
|
||||||
* Prevent navigation keys from triggering actions during IME composition (#36540)
|
|
||||||
* Fix vertical alignment of `.commit-sign-badge` children (#36570)
|
|
||||||
* Fix duplicate startup warnings in admin panel (#36641)
|
|
||||||
* Fix CODEOWNERS review request attribution using comment metadata (#36348)
|
|
||||||
* Fix HTML tags appearing in wiki table of contents (#36284)
|
|
||||||
* Fix various bugs (#37096)
|
|
||||||
* Fix various legacy problems (#37092)
|
|
||||||
* Fix RPM Registry 404 when package name contains 'package' (#37087)
|
|
||||||
* Merge some standalone Vite entries into index.js (#37085)
|
|
||||||
* Fix various problems (#37077)
|
|
||||||
* Fix issue label deletion with Actions tokens (#37013)
|
|
||||||
* Hide delete branch or tag buttons in mirror or archived repositories. (#37006)
|
|
||||||
* Fix org contact email not clearable once set (#36975)
|
|
||||||
* Fix a bug when forking a repository in an organization (#36950)
|
|
||||||
* Preserve sort order of exclusive labels from template repo (#36931)
|
|
||||||
* Make container registry support Apple Container (basic auth) (#36920)
|
|
||||||
* Fix the wrong push commits in the pull request when force push (#36914)
|
|
||||||
* Add class "list-header-filters" to the div for projects (#36889)
|
|
||||||
* Fix dbfs error handling (#36844)
|
|
||||||
* Fix incorrect viewed files counter if reverted change was viewed (#36819)
|
|
||||||
* Refactor avatar package, support default avatar fallback (#36788)
|
|
||||||
* Fix README symlink resolution in subdirectories like .github (#36775)
|
|
||||||
* Fix CSS stacking context issue in actions log (#36749)
|
|
||||||
* Add gpg signing for merge rebase and update by rebase (#36701)
|
|
||||||
* Delete non-exist branch should return 404 (#36694)
|
|
||||||
* Fix `TestActionsCollaborativeOwner` (#36657)
|
|
||||||
* Fix multi-arch Docker build SIGILL by splitting frontend stage (#36646)
|
|
||||||
* Fix linguist-detectable attribute being ignored for configuration files (#36640)
|
|
||||||
* Fix state desync in ComboMarkdownEditor (#36625)
|
|
||||||
* Unify DEFAULT_SHOW_FULL_NAME output in templates and dropdown (#36597)
|
|
||||||
* Pull Request Pusher should be the author of the merge (#36581)
|
|
||||||
* Fix various version parsing problems (#36553)
|
|
||||||
* Fix highlight diff result (#36539)
|
|
||||||
* Fix mirror sync parser and fix mirror messages (#36504)
|
|
||||||
* Fix bug when list pull request commits (#36485)
|
|
||||||
* Fix various bugs (#36446)
|
|
||||||
* Fix issue filter menu layout (#36426)
|
|
||||||
* Restrict branch naming when new change matches with protection rules (#36405)
|
|
||||||
* Fix link/origin referrer and login redirect (#36279)
|
|
||||||
* Generate IDs for HTML headings without id attribute (#36233)
|
|
||||||
* Use a migration test instead of a wrong test which populated the meta test repositories and fix a migration bug (#36160)
|
|
||||||
* Fix issue close timeline icon (#36138)
|
|
||||||
* Fix diff blob excerpt expansion (#35922)
|
|
||||||
* Fix external render (#35727)
|
|
||||||
* Fix review request webhook bug (#35339) (#35723)
|
|
||||||
* Fix shutdown waitgroup panic (#35676)
|
|
||||||
* Cleanup ActionRun creation (#35624)
|
|
||||||
* Fix possible bug when migrating issues/pull requests (#33487)
|
|
||||||
* Various fixes (#36697)
|
|
||||||
* Apply notify/register mail flags during install load (#37120)
|
|
||||||
* Repair duration display for bad stopped timestamps (#37121)
|
|
||||||
* Fix(upgrade.sh): use HTTPS for GPG key import and restore SELinux context after upgrade (#36930)
|
|
||||||
* Fix various trivial problems (#36921)
|
|
||||||
* Fix various trivial problems (#36953)
|
|
||||||
* Fix NuGet package upload error handling (#37074)
|
|
||||||
* Fix CodeQL code scanning alerts (#36858)
|
|
||||||
* Refactor issue sidebar and fix various problems (#37045)
|
|
||||||
* Fix various problems (#37029)
|
|
||||||
* Fix relative-time RangeError (#37021)
|
|
||||||
* Fix chroma lexer mapping (#36629)
|
|
||||||
* Fix typos and grammar in English locale (#36751)
|
|
||||||
* Fix milestone/project text overflow in issue sidebar (#36741)
|
|
||||||
* Fix `no-content` message not rendering after comment edit (#36733)
|
|
||||||
* Fix theme loading in development (#36605)
|
|
||||||
* Fix workflow run jobs API returning null steps (#36603)
|
|
||||||
* Fix timeline event layout overflow with long content (#36595)
|
|
||||||
* Fix minor UI issues in runner edit page (#36590)
|
|
||||||
* Fix incorrect vendored detections (#36508)
|
|
||||||
* Fix editorconfig not respected in PR Conversation view (#36492)
|
|
||||||
* Don't create self-references in merged PRs (#36490)
|
|
||||||
* Fix potential incorrect runID in run status update (#36437)
|
|
||||||
* Fix file-tree ui error when adding files to repo without commits (#36312)
|
|
||||||
* Improve image captcha contrast for dark mode (#36265)
|
|
||||||
* Fix panic in blame view when a file has only a single commit (#36230)
|
|
||||||
* Fix spelling error in migrate-storage cmd utility (#36226)
|
|
||||||
* Fix code highlighting on blame page (#36157)
|
|
||||||
* Fix nilnil in onedev downloader (#36154)
|
|
||||||
* Fix actions lint (#36029)
|
|
||||||
* Fix oauth2 session gob register (#36017)
|
|
||||||
* Fix Arch repo pacman.conf snippet (#35825)
|
|
||||||
* Fix a number of `strictNullChecks`-related issues (#35795)
|
|
||||||
* Fix URLJoin, markup render link reoslving, sign-in/up/linkaccount page common data (#36861)
|
|
||||||
* Hide delete directory button for mirror or archive repository and disable the menu item if user have no permission (#36384)
|
|
||||||
* Update message severity colors, fix navbar double border (#37019)
|
|
||||||
* Inline and lazy-load EasyMDE CSS, fix border colors (#36714)
|
|
||||||
* Closed milestones with no issues now show as 100% completed (#36220)
|
|
||||||
* Add test for ExtendCommentTreePathLength migration and fix bugs (#35791)
|
|
||||||
* Only turn links to current instance into hash links (#36237)
|
|
||||||
* Fix typos in code comments: doesnt, dont, wont (#36890)
|
|
||||||
* REFACTOR
|
|
||||||
* Clean up and improve non-gitea js error filter (#37148) (#37155)
|
|
||||||
* Always show owner/repo name in compare page dropdowns (#37172) (#37200)
|
|
||||||
* Remove dead CSS rules (#37173) (#37177)
|
|
||||||
* Replace Monaco with CodeMirror (#36764)
|
|
||||||
* Replace CSRF cookie with `CrossOriginProtection` (#36183)
|
|
||||||
* Replace index with id in actions routes (#36842)
|
|
||||||
* Remove unnecessary function parameter (#35765)
|
|
||||||
* Move jobparser from act repository to Gitea (#36699)
|
|
||||||
* Refactor compare router param parse (#36105)
|
|
||||||
* Optimize 'refreshAccesses' to perform update without removing then adding (#35702)
|
|
||||||
* Clean up checkbox cursor styles (#37016)
|
|
||||||
* Remove undocumented support of signing key in the repository git configuration file (#36143)
|
|
||||||
* Switch `cmd/` to use constructor functions. (#36962)
|
|
||||||
* Use `relative-time` to render absolute dates (#36238)
|
|
||||||
* Some refactors about GetMergeBase (#36186)
|
|
||||||
* Some small refactors (#36163)
|
|
||||||
* Use gitRepo as parameter instead of repopath when invoking sign functions (#36162)
|
|
||||||
* Move blame to gitrepo (#36161)
|
|
||||||
* Move some functions to gitrepo package to reduce RepoPath reference directly (#36126)
|
|
||||||
* Use gitrepo's clone and push when possible (#36093)
|
|
||||||
* Remove mermaid margin workaround (#35732)
|
|
||||||
* Move some functions to gitrepo package (#35543)
|
|
||||||
* Move GetDiverging functions to gitrepo (#35524)
|
|
||||||
* Use global lock instead of status pool for cron lock (#35507)
|
|
||||||
* Use explicit mux instead of DefaultServeMux (#36276)
|
|
||||||
* Use gitrepo's push function (#36245)
|
|
||||||
* Pass request context to generateAdditionalHeadersForIssue (#36274)
|
|
||||||
* Move assign project when creating pull request to the same database transaction (#36244)
|
|
||||||
* Move catfile batch to a sub package of git module (#36232)
|
|
||||||
* Use gitrepo.Repository instead of wikipath (#35398)
|
|
||||||
* Use experimental go json v2 library (#35392)
|
|
||||||
* Refactor template render (#36438)
|
|
||||||
* Refactor GetRepoRawDiffForFile to avoid unnecessary pipe or goroutine (#36434)
|
|
||||||
* Refactor text utility classes to Tailwind CSS (#36703)
|
|
||||||
* Refactor git command stdio pipe (#36422)
|
|
||||||
* Refactor git command context & pipeline (#36406)
|
|
||||||
* Refactor git command stdio pipe (#36393)
|
|
||||||
* Remove unused functions (#36672)
|
|
||||||
* Refactor Actions Token Access (#35688)
|
|
||||||
* Move commit related functions to gitrepo package (#35600)
|
|
||||||
* Move archive function to repo_model and gitrepo (#35514)
|
|
||||||
* Move some functions to gitrepo package (#35503)
|
|
||||||
* Use git model to detect whether branch exist instead of gitrepo method (#35459)
|
|
||||||
* Some refactor for repo path (#36251)
|
|
||||||
* Extract helper functions from SearchIssues (#36158)
|
|
||||||
* Refactor merge conan and container auth preserve actions taskID (#36560)
|
|
||||||
* Refactor Nuget Auth to reuse Basic Auth Token Validation (#36558)
|
|
||||||
* Refactor ActionsTaskID (#36503)
|
|
||||||
* Refactor auth middleware (#36848)
|
|
||||||
* Refactor code render and render control chars (#37078)
|
|
||||||
* Clean up AppURL, remove legacy origin-url webcomponent (#37090)
|
|
||||||
* Remove `util.URLJoin` and replace all callers with direct path concatenation (#36867)
|
|
||||||
* Replace legacy tw-flex utility classes with flex-text-block/inline (#36778)
|
|
||||||
* Mark unused&immature activitypub as "not implemented" (#36789)
|
|
||||||
* TESTING
|
|
||||||
* Add e2e tests for server push events (#36879)
|
|
||||||
* Rework e2e tests (#36634)
|
|
||||||
* Add e2e reaction test, improve accessibility, enable parallel testing (#37081)
|
|
||||||
* Increase e2e test timeouts on CI to fix flaky tests (#37053)
|
|
||||||
* BUILD
|
|
||||||
* Upgrade go-git to v5.18.0 (#37269)
|
|
||||||
* Replace rollup-plugin-license with rolldown-license-plugin (#37130) (#37158)
|
|
||||||
* Bump min go version to 1.26.2 (#37139) (#37143)
|
|
||||||
* Convert locale files from ini to json format (#35489)
|
|
||||||
* Bump golangci-lint to 2.7.2, enable modernize stringsbuilder (#36180)
|
|
||||||
* Port away from `flake-utils` (#35675)
|
|
||||||
* Remove nolint (#36252)
|
|
||||||
* Update the Unlicense copy to latest version (#36636)
|
|
||||||
* Update to go 1.26.0 and golangci-lint 2.9.0 (#36588)
|
|
||||||
* Replace `google/go-licenses` with custom generation (#36575)
|
|
||||||
* Update go dependencies (#36548)
|
|
||||||
* Bump appleboy/git-push-action from 1.0.0 to 1.2.0 (#36306)
|
|
||||||
* Remove fomantic form module (#36222)
|
|
||||||
* Bump setup-node to v6, re-enable cache (#36207)
|
|
||||||
* Bump crowdin/github-action from 1 to 2 (#36204)
|
|
||||||
* Revert "Bump alpine to 3.23 (#36185)" (#36202)
|
|
||||||
* Update chroma to v2.21.1 (#36201)
|
|
||||||
* Bump astral-sh/setup-uv from 6 to 7 (#36198)
|
|
||||||
* Bump docker/build-push-action from 5 to 6 (#36197)
|
|
||||||
* Bump aws-actions/configure-aws-credentials from 4 to 5 (#36196)
|
|
||||||
* Bump dev-hanz-ops/install-gh-cli-action from 0.1.0 to 0.2.1 (#36195)
|
|
||||||
* Add JSON linting (#36192)
|
|
||||||
* Enable dependabot for actions (#36191)
|
|
||||||
* Bump alpine to 3.23 (#36185)
|
|
||||||
* Update chroma to v2.21.0 (#36171)
|
|
||||||
* Update JS deps and eslint enhancements (#36147)
|
|
||||||
* Update JS deps (#36091)
|
|
||||||
* update golangci-lint to v2.7.0 (#36079)
|
|
||||||
* Update JS deps, fix deprecations (#36040)
|
|
||||||
* Update JS deps (#35978)
|
|
||||||
* Add toolchain directive to go.mod (#35901)
|
|
||||||
* Move `gitea-vet` to use `go tool` (#35878)
|
|
||||||
* Update to go 1.25.4 (#35877)
|
|
||||||
* Enable TypeScript `strictNullChecks` (#35843)
|
|
||||||
* Enable `vue/require-typed-ref` eslint rule (#35764)
|
|
||||||
* Update JS dependencies (#35759)
|
|
||||||
* Move `codeformat` folder to tools (#35758)
|
|
||||||
* Update dependencies (#35733)
|
|
||||||
* Bump happy-dom from 20.0.0 to 20.0.2 (#35677)
|
|
||||||
* Bump setup-go to v6 (#35660)
|
|
||||||
* Update JS deps, misc tweaks (#35643)
|
|
||||||
* Bump happy-dom from 19.0.2 to 20.0.0 (#35625)
|
|
||||||
* Use bundled version of spectral (#35573)
|
|
||||||
* Update JS and PY deps (#35565)
|
|
||||||
* Bump github.com/wneessen/go-mail from 0.6.2 to 0.7.1 (#35557)
|
|
||||||
* Migrate from webpack to vite (#37002)
|
|
||||||
* Update JS dependencies and misc tweaks (#37064)
|
|
||||||
* Update to eslint 10 (#36925)
|
|
||||||
* Optimize Docker build with dependency layer caching (#36864)
|
|
||||||
* Update JS deps (#36850)
|
|
||||||
* Update tool dependencies and fix new lint issues (#36702)
|
|
||||||
* Remove redundant linter rules (#36658)
|
|
||||||
* Move Fomantic dropdown CSS to custom module (#36530)
|
|
||||||
* Remove and forbid `@ts-expect-error` (#36513)
|
|
||||||
* Refactor git command stderr handling (#36402)
|
|
||||||
* Enable gocheckcompilerdirectives linter (#36156)
|
|
||||||
* Replace `lint-go-gopls` with additional `govet` linters (#36028)
|
|
||||||
* Update golangci-lint to v2.6.0 (#35801)
|
|
||||||
* Misc tool tweaks (#35734)
|
|
||||||
* Add cache to container build (#35697)
|
|
||||||
* Upgrade vite (#37126)
|
|
||||||
* Update `setup-uv` to v8.0.0 (#37101)
|
|
||||||
* Upgrade `go-git` to v5.17.2 and related dependencies (#37060)
|
|
||||||
* Raise minimum Node.js version to 22.18.0 (#37058)
|
|
||||||
* Upgrade `golang.org/x/image` to v0.38.0 (#37054)
|
|
||||||
* Update minimum go version to 1.26.1, golangci-lint to 2.11.2, fix test style (#36876)
|
|
||||||
* Enable eslint concurrency (#36878)
|
|
||||||
* Vendor relative-time-element as local web component (#36853)
|
|
||||||
* Update material-icon-theme v5.32.0 (#36832)
|
|
||||||
* Update Go dependencies (#36781)
|
|
||||||
* Upgrade minimatch (#36760)
|
|
||||||
* Remove i18n backport tool at the moment because of translation format changed (#36643)
|
|
||||||
* Update emoji data for Unicode 16 (#36596)
|
|
||||||
* Update JS dependencies, adjust webpack config, misc fixes (#36431)
|
|
||||||
* Update material-icon-theme to v5.31.0 (#36427)
|
|
||||||
* Update JS and PY deps (#36383)
|
|
||||||
* Bump alpine to 3.23, add platforms to `docker-dryrun` (#36379)
|
|
||||||
* Update JS deps (#36354)
|
|
||||||
* Update goldmark to v1.7.16 (#36343)
|
|
||||||
* Update chroma to v2.22.0 (#36342)
|
|
||||||
* DOCS
|
|
||||||
* Update AI Contribution Policy (#37022)
|
|
||||||
* Update AGENTS.md with additional guidelines (#37018)
|
|
||||||
* Add missing cron tasks to example ini (#37012)
|
|
||||||
* Add AI Contribution Policy to CONTRIBUTING.md (#36651)
|
|
||||||
* Minor punctuation improvement in CONTRIBUTING.md (#36291)
|
|
||||||
* Add documentation for markdown anchor post-processing (#36443)
|
|
||||||
* MISC
|
|
||||||
* Correct spelling (#36783)
|
|
||||||
* Update Nix flake (#37110)
|
|
||||||
* Update Nix flake (#37024)
|
|
||||||
* Add valid github scopes (#36977)
|
|
||||||
* Update Nix flake (#36943)
|
|
||||||
* Update Nix flake (#36902)
|
|
||||||
* Update Nix flake (#36857)
|
|
||||||
* Update Nix flake (#36787)
|
|
||||||
|
|
||||||
## [1.25.5](https://github.com/go-gitea/gitea/releases/tag/v1.25.5) - 2026-03-10
|
|
||||||
|
|
||||||
* SECURITY
|
|
||||||
* Toolchain Update to Go 1.25.6 (#36480) (#36487)
|
|
||||||
* Adjust the toolchain version (#36537) (#36542)
|
|
||||||
* Update toolchain to 1.25.8 for v1.25 (#36888)
|
|
||||||
* Prevent redirect bypasses via backslash-encoded paths (#36660) (#36716)
|
|
||||||
* Fix get release draft permission check (#36659) (#36715)
|
|
||||||
* Fix a bug user could change another user's primary email (#36586) (#36607)
|
|
||||||
* Fix OAuth2 authorization code expiry and reuse handling (#36797) (#36851)
|
|
||||||
* Add validation constraints for repository creation fields (#36671) (#36757)
|
|
||||||
* Fix bug to check whether user can update pull request branch or rebase branch (#36465) (#36838)
|
|
||||||
* Add migration http transport for push/sync mirror lfs (#36665) (#36691)
|
|
||||||
* Fix track time list permission check (#36662) (#36744)
|
|
||||||
* Fix track time issue id (#36664) (#36689)
|
|
||||||
* Fix path resolving (#36734) (#36746)
|
|
||||||
* Fix dump release asset bug (#36799) (#36839)
|
|
||||||
* Fix org permission API visibility checks for hidden members and private orgs (#36798) (#36841)
|
|
||||||
* Fix forwarded proto handling for public URL detection (#36810) (#36836)
|
|
||||||
* Add a git grep search timeout (#36809) (#36835)
|
|
||||||
* Fix oauth2 s256 (#36462) (#36477)
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Make `security-check` informational only (#36681) (#36852)
|
|
||||||
* Upgrade to github.com/cloudflare/circl 1.6.3, svgo 4.0.1, markdownlint-cli 0.48.0 (#36840)
|
|
||||||
* Add some validation on values provided to USER_DISABLED_FEATURES and EXTERNAL_USER_DISABLED_FEATURES (#36688) (#36692)
|
|
||||||
* Upgrade gogit to 5.16.5 (#36687)
|
|
||||||
* Add wrap to runner label list (#36565) (#36574)
|
|
||||||
* Add dnf5 command for Fedora in RPM package instructions (#36527) (#36572)
|
|
||||||
* Allow scroll propagation outside code editor (#36502) (#36510)
|
|
||||||
* BUGFIXES
|
|
||||||
* Fix non-admins unable to automerge PRs from forks (#36833) (#36843)
|
|
||||||
* Fix bug when pushing mirror with wiki (#36795) (#36807)
|
|
||||||
* Fix artifacts v4 backend upload problems (#36805) (#36834)
|
|
||||||
* Fix CRAN package version validation to allow more than 4 version components (#36813) (#36821)
|
|
||||||
* Fix force push time-line commit comments of pull request (#36653) (#36717)
|
|
||||||
* Fix SVG height calculation in diff viewer (#36748) (#36750)
|
|
||||||
* Fix push time bug (#36693) (#36713)
|
|
||||||
* Fix bug the protected branch rule name is conflicted with renamed branch name (#36650) (#36661)
|
|
||||||
* Fix bug when do LFS GC (#36500) (#36608)
|
|
||||||
* Fix focus lost bugs in the Monaco editor (#36609)
|
|
||||||
* Reprocess htmx content after loading more files (#36568) (#36577)
|
|
||||||
* Fix assignee sidebar links and empty placeholder (#36559) (#36563)
|
|
||||||
* Fix issues filter dropdown showing empty label scope section (#36535) (#36544)
|
|
||||||
* Fix various mermaid bugs (#36547) (#36552)
|
|
||||||
* Fix data race when uploading container blobs concurrently (#36524) (#36526)
|
|
||||||
* Correct spacing between username and bot label (#36473) (#36484)
|
|
||||||
|
|
||||||
## [1.25.4](https://github.com/go-gitea/gitea/releases/tag/v1.25.4) - 2026-01-15
|
|
||||||
|
|
||||||
* SECURITY
|
|
||||||
* Release attachments must belong to the intended repo (#36347) (#36375)
|
|
||||||
* Fix permission check on org project operations (#36318) (#36373)
|
|
||||||
* Clean watches when make a repository private and check permission when send release emails (#36319) (#36370)
|
|
||||||
* Add more check for stopwatch read or list (#36340) (#36368)
|
|
||||||
* Fix openid setting check (#36346) (#36361)
|
|
||||||
* Fix cancel auto merge bug (#36341) (#36356)
|
|
||||||
* Fix delete attachment check (#36320) (#36355)
|
|
||||||
* LFS locks must belong to the intended repo (#36344) (#36349)
|
|
||||||
* Fix bug on notification read (#36339) #36387
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Add more routes to the "expensive" list (#36290)
|
|
||||||
* Make "commit statuses" API accept slashes in "ref" (#36264) (#36275)
|
|
||||||
* BUGFIXES
|
|
||||||
* Fix git http service handling (#36396)
|
|
||||||
* Fix markdown newline handling during IME composition (#36421) (#36424)
|
|
||||||
* Fix missing repository id when migrating release attachments (#36389)
|
|
||||||
* Fix bug when compare in the pull request (#36363) (#36372)
|
|
||||||
* Fix incorrect text content detection (#36364) (#36369)
|
|
||||||
* Fill missing `has_code` in repository api (#36338) (#36359)
|
|
||||||
* Fix notifications pagination query parameters (#36351) (#36358)
|
|
||||||
* Fix some trivial problems (#36336) (#36337)
|
|
||||||
* Prevent panic when GitLab release has more links than sources (#36295) (#36305)
|
|
||||||
* Fix stats bug when syncing release (#36285) (#36294)
|
|
||||||
* Always honor user's choice for "delete branch after merge" (#36281) (#36286)
|
|
||||||
* Use the requested host for LFS links (#36242) (#36258)
|
|
||||||
* Fix panic when get editor config file (#36241) (#36247)
|
|
||||||
* Fix regression in writing authorized principals (#36213) (#36218)
|
|
||||||
* Fix WebAuthn error checking (#36219) (#36235)
|
|
||||||
|
|
||||||
## [1.25.3](https://github.com/go-gitea/gitea/releases/tag/v1.25.3) - 2025-12-17
|
|
||||||
|
|
||||||
* 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/v1.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
|
* BUGFIXES
|
||||||
* Fix container range bug (#34795) (#34796)
|
* Fix container range bug (#34795) (#34796)
|
||||||
@@ -877,7 +12,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* BUILD
|
* BUILD
|
||||||
* Bump poetry feature to new url for dev container (#34787) (#34790)
|
* 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
|
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/1.24.1) - 2025-06-18
|
||||||
|
|
||||||
* ENHANCEMENTS
|
* ENHANCEMENTS
|
||||||
* Improve alignment of commit status icon on commit page (#34750) (#34757)
|
* Improve alignment of commit status icon on commit page (#34750) (#34757)
|
||||||
@@ -897,7 +32,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
|
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
|
||||||
* Fix tag target (#34781) #34783
|
* Fix tag target (#34781) #34783
|
||||||
|
|
||||||
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/v1.24.0) - 2025-05-26
|
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.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)
|
||||||
@@ -1267,7 +402,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* Bump x/net (#32896) (#32900)
|
* Bump x/net (#32896) (#32900)
|
||||||
* Only activity tab needs heatmap data loading (#34652)
|
* 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
|
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
||||||
@@ -1294,7 +429,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* Bump go version in go.mod (#34160)
|
* Bump go version in go.mod (#34160)
|
||||||
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
* 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
|
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
|
||||||
|
|
||||||
* Enhancements
|
* Enhancements
|
||||||
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
||||||
@@ -1392,7 +527,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)
|
||||||
@@ -3922,7 +3057,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
|
||||||
@@ -3935,7 +3070,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)
|
||||||
@@ -4434,7 +3569,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)
|
||||||
@@ -4861,7 +3996,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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
445
CONTRIBUTING.md
445
CONTRIBUTING.md
@@ -1,25 +1,19 @@
|
|||||||
# Contribution Guidelines
|
# Contribution Guidelines
|
||||||
|
|
||||||
This document explains how to contribute changes to the Gitea project. Topic-specific guides live in separate files so the essentials are easier to find.
|
|
||||||
|
|
||||||
| Topic | Document |
|
|
||||||
| :---- | :------- |
|
|
||||||
| Backend (Go modules, API v1) | [docs/guideline-backend.md](docs/guideline-backend.md) |
|
|
||||||
| Frontend (npm, UI guidelines) | [docs/guideline-frontend.md](docs/guideline-frontend.md) |
|
|
||||||
| Maintainers, TOC, labels, merge queue, commit format for mergers | [docs/community-governance.md](docs/community-governance.md) |
|
|
||||||
| Release cycle, backports, tagging releases | [docs/release-management.md](docs/release-management.md) |
|
|
||||||
|
|
||||||
<details><summary>Table of Contents</summary>
|
<details><summary>Table of Contents</summary>
|
||||||
|
|
||||||
- [Contribution Guidelines](#contribution-guidelines)
|
- [Contribution Guidelines](#contribution-guidelines)
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [AI Contribution Policy](#ai-contribution-policy)
|
|
||||||
- [Issues](#issues)
|
- [Issues](#issues)
|
||||||
- [How to report issues](#how-to-report-issues)
|
- [How to report issues](#how-to-report-issues)
|
||||||
- [Types of issues](#types-of-issues)
|
- [Types of issues](#types-of-issues)
|
||||||
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
|
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
|
||||||
- [Issue locking](#issue-locking)
|
- [Issue locking](#issue-locking)
|
||||||
- [Building Gitea](#building-gitea)
|
- [Building Gitea](#building-gitea)
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [Backend](#backend)
|
||||||
|
- [Frontend](#frontend)
|
||||||
|
- [Design guideline](#design-guideline)
|
||||||
- [Styleguide](#styleguide)
|
- [Styleguide](#styleguide)
|
||||||
- [Copyright](#copyright)
|
- [Copyright](#copyright)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
@@ -27,39 +21,52 @@ This document explains how to contribute changes to the Gitea project. Topic-spe
|
|||||||
- [Code review](#code-review)
|
- [Code review](#code-review)
|
||||||
- [Pull request format](#pull-request-format)
|
- [Pull request format](#pull-request-format)
|
||||||
- [PR title and summary](#pr-title-and-summary)
|
- [PR title and summary](#pr-title-and-summary)
|
||||||
|
- [Milestone](#milestone)
|
||||||
|
- [Labels](#labels)
|
||||||
- [Breaking PRs](#breaking-prs)
|
- [Breaking PRs](#breaking-prs)
|
||||||
- [What is a breaking PR?](#what-is-a-breaking-pr)
|
- [What is a breaking PR?](#what-is-a-breaking-pr)
|
||||||
- [How to handle breaking PRs?](#how-to-handle-breaking-prs)
|
- [How to handle breaking PRs?](#how-to-handle-breaking-prs)
|
||||||
- [Maintaining open PRs](#maintaining-open-prs)
|
- [Maintaining open PRs](#maintaining-open-prs)
|
||||||
- [Reviewing PRs](#reviewing-prs)
|
- [Getting PRs merged](#getting-prs-merged)
|
||||||
- [For PR authors](#for-pr-authors)
|
- [Final call](#final-call)
|
||||||
|
- [Commit messages](#commit-messages)
|
||||||
|
- [PR Co-authors](#pr-co-authors)
|
||||||
|
- [PRs targeting `main`](#prs-targeting-main)
|
||||||
|
- [Backport PRs](#backport-prs)
|
||||||
- [Documentation](#documentation)
|
- [Documentation](#documentation)
|
||||||
|
- [API v1](#api-v1)
|
||||||
|
- [GitHub API compatibility](#github-api-compatibility)
|
||||||
|
- [Adding/Maintaining API routes](#addingmaintaining-api-routes)
|
||||||
|
- [When to use what HTTP method](#when-to-use-what-http-method)
|
||||||
|
- [Requirements for API routes](#requirements-for-api-routes)
|
||||||
|
- [Backports and Frontports](#backports-and-frontports)
|
||||||
|
- [What is backported?](#what-is-backported)
|
||||||
|
- [How to backport?](#how-to-backport)
|
||||||
|
- [Format of backport PRs](#format-of-backport-prs)
|
||||||
|
- [Frontports](#frontports)
|
||||||
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
|
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
|
||||||
|
- [Release Cycle](#release-cycle)
|
||||||
|
- [Maintainers](#maintainers)
|
||||||
|
- [Technical Oversight Committee (TOC)](#technical-oversight-committee-toc)
|
||||||
|
- [TOC election process](#toc-election-process)
|
||||||
|
- [Current TOC members](#current-toc-members)
|
||||||
|
- [Previous TOC/owners members](#previous-tocowners-members)
|
||||||
|
- [Governance Compensation](#governance-compensation)
|
||||||
|
- [TOC \& Working groups](#toc--working-groups)
|
||||||
|
- [Roadmap](#roadmap)
|
||||||
|
- [Versions](#versions)
|
||||||
|
- [Releasing Gitea](#releasing-gitea)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
|
This document explains how to contribute changes to the Gitea project. \
|
||||||
It assumes you have followed the [installation instructions](https://docs.gitea.com/category/installation). \
|
It assumes you have followed the [installation instructions](https://docs.gitea.com/category/installation). \
|
||||||
Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
|
Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
|
||||||
|
|
||||||
For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/).
|
For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/).
|
||||||
|
|
||||||
## AI Contribution Policy
|
|
||||||
|
|
||||||
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly.
|
|
||||||
|
|
||||||
1. Review AI-generated code closely before marking a pull request ready for review.
|
|
||||||
2. Manually test the changes and add appropriate automated tests where feasible.
|
|
||||||
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
|
|
||||||
4. Disclose AI-assisted content clearly.
|
|
||||||
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
|
|
||||||
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
|
|
||||||
|
|
||||||
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
|
|
||||||
|
|
||||||
We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
|
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
### How to report issues
|
### How to report issues
|
||||||
@@ -73,7 +80,7 @@ The more detailed and specific you are, the faster we can fix the issue. \
|
|||||||
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \
|
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \
|
||||||
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
|
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
|
||||||
|
|
||||||
Please be kind—remember that Gitea comes at no cost to you, and you're getting free help.
|
Please be kind, remember that Gitea comes at no cost to you, and you're getting free help.
|
||||||
|
|
||||||
### Types of issues
|
### Types of issues
|
||||||
|
|
||||||
@@ -108,6 +115,34 @@ If further discussion is needed, we encourage you to open a new issue instead an
|
|||||||
|
|
||||||
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
|
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
Go dependencies are managed using [Go Modules](https://go.dev/cmd/go/#hdr-Module_maintenance). \
|
||||||
|
You can find more details in the [go mod documentation](https://go.dev/ref/mod) and the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules).
|
||||||
|
|
||||||
|
Pull requests should only modify `go.mod` and `go.sum` where it is related to your change, be it a bugfix or a new feature. \
|
||||||
|
Apart from that, these files should only be modified by Pull Requests whose only purpose is to update dependencies.
|
||||||
|
|
||||||
|
The `go.mod`, `go.sum` update needs to be justified as part of the PR description,
|
||||||
|
and must be verified by the reviewers and/or merger to always reference
|
||||||
|
an existing upstream commit.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
For the frontend, we use [npm](https://www.npmjs.com/).
|
||||||
|
|
||||||
|
The same restrictions apply for frontend dependencies as for backend dependencies, with the exceptions that the files for it are `package.json` and `package-lock.json`, and that new versions must always reference an existing version.
|
||||||
|
|
||||||
|
## Design guideline
|
||||||
|
|
||||||
|
Depending on your change, please read the
|
||||||
|
|
||||||
|
- [backend development guideline](https://docs.gitea.com/contributing/guidelines-backend)
|
||||||
|
- [frontend development guideline](https://docs.gitea.com/contributing/guidelines-frontend)
|
||||||
|
- [refactoring guideline](https://docs.gitea.com/contributing/guidelines-refactoring)
|
||||||
|
|
||||||
## Styleguide
|
## Styleguide
|
||||||
|
|
||||||
You should always run `make fmt` before committing to conform to Gitea's styleguide.
|
You should always run `make fmt` before committing to conform to Gitea's styleguide.
|
||||||
@@ -131,32 +166,24 @@ Here's how to run the test suite:
|
|||||||
|
|
||||||
- code lint
|
- code lint
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| :-------------------- | :--------------------------------------------------------------------------- |
|
| :-------------------- | :---------------------------------------------------------------- |
|
||||||
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|
||||||
|``make lint-frontend`` | lint frontend files |
|
|``make lint-frontend`` | lint frontend files |
|
||||||
|``make lint-backend`` | lint backend files |
|
|``make lint-backend`` | lint backend files |
|
||||||
|
|
||||||
- run tests (we suggest running them on Linux)
|
- run tests (we suggest running them on Linux)
|
||||||
|
|
||||||
| Command | Action | |
|
| Command | Action | |
|
||||||
| :------------------------------------------ | :------------------------------------------------------- | ------------------------------------------- |
|
| :------------------------------------- | :----------------------------------------------- | ------------ |
|
||||||
|``make test[\#SpecificTestName]`` | run unit test(s) | |
|
|``make test[\#SpecificTestName]`` | run unit test(s) | |
|
||||||
|``make test-sqlite[\#SpecificTestName]`` | run [integration](tests/integration) test(s) for SQLite | [More details](tests/integration/README.md) |
|
|``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) |
|
||||||
|``make test-e2e`` | run [end-to-end](tests/e2e) test(s) using Playwright | |
|
|``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/README.md) |
|
||||||
|
|
||||||
- E2E test environment variables
|
|
||||||
|
|
||||||
| Variable | Description |
|
|
||||||
| :-------------------------------- | :---------------------------------------------------------- |
|
|
||||||
| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output |
|
|
||||||
| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` |
|
|
||||||
| ``GITEA_TEST_E2E_TIMEOUT_FACTOR`` | Timeout multiplier (default: 3 on CI, 1 locally) |
|
|
||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
All translation work happens on [Crowdin](https://translate.gitea.com).
|
All translation work happens on [Crowdin](https://translate.gitea.com).
|
||||||
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.json).
|
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini).
|
||||||
It is synced regularly with Crowdin. \
|
It is synced regularly with Crowdin. \
|
||||||
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
||||||
Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version.
|
Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version.
|
||||||
@@ -165,8 +192,6 @@ The tool `go run build/backport-locale.go` can be used to backport locales from
|
|||||||
|
|
||||||
## Code review
|
## Code review
|
||||||
|
|
||||||
How labels, milestones, and the merge queue work is documented in [docs/community-governance.md](docs/community-governance.md).
|
|
||||||
|
|
||||||
### Pull request format
|
### Pull request format
|
||||||
|
|
||||||
Please try to make your pull request easy to review for us. \
|
Please try to make your pull request easy to review for us. \
|
||||||
@@ -211,6 +236,29 @@ Fixes/Closes/Resolves #<ISSUE_NR_Y>.
|
|||||||
to your summary. \
|
to your summary. \
|
||||||
Each issue that will be closed must stand on a separate line.
|
Each issue that will be closed must stand on a separate line.
|
||||||
|
|
||||||
|
### Milestone
|
||||||
|
|
||||||
|
A PR should only be assigned to a milestone if it will likely be merged into the given version. \
|
||||||
|
As a rule of thumb, assume that a PR will stay open for an additional month for every 100 added lines. \
|
||||||
|
PRs without a milestone may not be merged.
|
||||||
|
|
||||||
|
### Labels
|
||||||
|
|
||||||
|
Almost all labels used inside Gitea can be classified as one of the following:
|
||||||
|
|
||||||
|
- `modifies/…`: Determines which parts of the codebase are affected. These labels will be set through the CI.
|
||||||
|
- `topic/…`: Determines the conceptual component of Gitea that is affected, i.e. issues, projects, or authentication. At best, PRs should only target one component but there might be overlap. Must be set manually.
|
||||||
|
- `type/…`: Determines the type of an issue or PR (feature, refactoring, docs, bug, …). If GitHub supported scoped labels, these labels would be exclusive, so you should set **exactly** one, not more or less (every PR should fall into one of the provided categories, and only one).
|
||||||
|
- `issue/…` / `pr/…`: Labels that are specific to issues or PRs respectively and that are only necessary in a given context, i.e. `issue/not-a-bug` or `pr/need-2-approvals`
|
||||||
|
|
||||||
|
Every PR should be labeled correctly with every label that applies.
|
||||||
|
|
||||||
|
There are also some labels that will be managed automatically.\
|
||||||
|
In particular, these are
|
||||||
|
|
||||||
|
- the amount of pending required approvals
|
||||||
|
- has all `backport`s or needs a manual backport
|
||||||
|
|
||||||
### Breaking PRs
|
### Breaking PRs
|
||||||
|
|
||||||
#### What is a breaking PR?
|
#### What is a breaking PR?
|
||||||
@@ -239,29 +287,165 @@ Breaking PRs will not be merged as long as not both of these requirements are me
|
|||||||
|
|
||||||
### Maintaining open PRs
|
### Maintaining open PRs
|
||||||
|
|
||||||
Code review starts when you open a non-draft PR or move a draft out of draft state. After that, do not rebase or squash your branch; it makes new changes harder to review.
|
The moment you create a non-draft PR or the moment you convert a draft PR to a non-draft PR is the moment code review starts for it. \
|
||||||
|
Once that happens, do not rebase or squash your branch anymore as it makes it difficult to review the new changes. \
|
||||||
|
Merge the base branch into your branch only when you really need to, i.e. because of conflicting changes in the mean time. \
|
||||||
|
This reduces unnecessary CI runs. \
|
||||||
|
Don't worry about merge commits messing up your commit history as every PR will be squash merged. \
|
||||||
|
This means that all changes are joined into a single new commit whose message is as described below.
|
||||||
|
|
||||||
Merge the base branch into yours only when you need to, for example because of conflicting changes elsewhere. That limits unnecessary CI runs.
|
### Getting PRs merged
|
||||||
|
|
||||||
Every PR is squash-merged, so merge commits on your branch do not matter for final history. The squash produces a single commit; mergers follow the [commit message format](docs/community-governance.md#commit-messages) in the governance guide.
|
Changes to Gitea must be reviewed before they are accepted — no matter who
|
||||||
|
makes the change, even if they are an owner or a maintainer. \
|
||||||
|
The only exception are critical bugs that prevent Gitea from being compiled or started. \
|
||||||
|
Specifically, we require two approvals from maintainers for every PR. \
|
||||||
|
Once this criteria has been met, your PR receives the `lgtm/done` label. \
|
||||||
|
From this point on, your only responsibility is to fix merge conflicts or respond to/implement requests by maintainers. \
|
||||||
|
It is the responsibility of the maintainers from this point to get your PR merged.
|
||||||
|
|
||||||
### Reviewing PRs
|
If a PR has the `lgtm/done` label and there are no open discussions or merge conflicts anymore, any maintainer can add the `reviewed/wait-merge` label. \
|
||||||
|
This label means that the PR is part of the merge queue and will be merged as soon as possible. \
|
||||||
|
The merge queue will be cleared in the order of the list below:
|
||||||
|
|
||||||
Maintainers are encouraged to review pull requests in areas where they have expertise or particular interest.
|
<https://github.com/go-gitea/gitea/pulls?q=is%3Apr+label%3Areviewed%2Fwait-merge+sort%3Acreated-asc+is%3Aopen>
|
||||||
|
|
||||||
#### For PR authors
|
Gitea uses it's own tool, the <https://github.com/GiteaBot/gitea-backporter> to automate parts of the review process. \
|
||||||
|
This tool does the things listed below automatically:
|
||||||
|
|
||||||
- **Response**: When answering reviewer questions, use real-world cases or examples and avoid speculation.
|
- create a backport PR if needed once the initial PR was merged
|
||||||
- **Discussion**: A discussion is always welcome and should be used to clarify the changes and the intent of the PR.
|
- remove the PR from the merge queue after the PR merged
|
||||||
- **Help**: If you need help with the PR or comments are unclear, ask for clarification.
|
- keep the oldest branch in the merge queue up to date with merges
|
||||||
|
|
||||||
Guidance for reviewers, the merge queue, and the squash commit message format is in [docs/community-governance.md](docs/community-governance.md).
|
### Final call
|
||||||
|
|
||||||
|
If a PR has been ignored for more than 7 days with no comments or reviews, and the author or any maintainer believes it will not survive a long wait (such as a refactoring PR), they can send "final call" to the TOC by mentioning them in a comment.
|
||||||
|
|
||||||
|
After another 7 days, if there is still zero approval, this is considered a polite refusal, and the PR will be closed to avoid wasting further time. Therefore, the "final call" has a cost, and should be used cautiously.
|
||||||
|
|
||||||
|
However, if there are no objections from maintainers, the PR can be merged with only one approval from the TOC (not the author).
|
||||||
|
|
||||||
|
### Commit messages
|
||||||
|
|
||||||
|
Mergers are able and required to rewrite the PR title and summary (the first comment of a PR) so that it can produce an easily understandable commit message if necessary. \
|
||||||
|
The final commit message should no longer contain any uncertainty such as `hopefully, <x> won't happen anymore`. Replace uncertainty with certainty.
|
||||||
|
|
||||||
|
#### PR Co-authors
|
||||||
|
|
||||||
|
A person counts as a PR co-author the moment they (co-)authored a commit that is not simply a `Merge base branch into branch` commit. \
|
||||||
|
Mergers are required to remove such "false-positive" co-authors when writing the commit message. \
|
||||||
|
The true co-authors must remain in the commit message.
|
||||||
|
|
||||||
|
#### PRs targeting `main`
|
||||||
|
|
||||||
|
The commit message of PRs targeting `main` is always
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$PR_TITLE ($PR_INDEX)
|
||||||
|
|
||||||
|
$REWRITTEN_PR_SUMMARY
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Backport PRs
|
||||||
|
|
||||||
|
The commit message of backport PRs is always
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$PR_TITLE ($INITIAL_PR_INDEX) ($BACKPORT_PR_INDEX)
|
||||||
|
|
||||||
|
$REWRITTEN_PR_SUMMARY
|
||||||
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in another PR at [https://gitea.com/gitea/docs](https://gitea.com/gitea/docs).
|
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in another PR at [https://gitea.com/gitea/docs](https://gitea.com/gitea/docs).
|
||||||
**The docs directory on main repository will be removed at some time. We will have a yaml file to store configuration file's meta data. After that completed, configuration documentation should be in the main repository.**
|
**The docs directory on main repository will be removed at some time. We will have a yaml file to store configuration file's meta data. After that completed, configuration documentation should be in the main repository.**
|
||||||
|
|
||||||
|
## API v1
|
||||||
|
|
||||||
|
The API is documented by [swagger](https://gitea.com/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest).
|
||||||
|
|
||||||
|
### GitHub API compatibility
|
||||||
|
|
||||||
|
Gitea's API should use the same endpoints and fields as the GitHub API as far as possible, unless there are good reasons to deviate. \
|
||||||
|
If Gitea provides functionality that GitHub does not, a new endpoint can be created. \
|
||||||
|
If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields. \
|
||||||
|
Updating an existing API should not remove existing fields unless there is a really good reason to do so. \
|
||||||
|
The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to API v2 (which is currently not planned).
|
||||||
|
|
||||||
|
### Adding/Maintaining API routes
|
||||||
|
|
||||||
|
All expected results (errors, success, fail messages) must be documented ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)). \
|
||||||
|
All JSON input types must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91)) \
|
||||||
|
and referenced in [routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go). \
|
||||||
|
They can then be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318). \
|
||||||
|
All JSON responses must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L36-L68)) \
|
||||||
|
and referenced in its category in [routers/api/v1/swagger/](routers/api/v1/swagger/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16)) \
|
||||||
|
They can be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L277-L279).
|
||||||
|
|
||||||
|
### When to use what HTTP method
|
||||||
|
|
||||||
|
In general, HTTP methods are chosen as follows:
|
||||||
|
|
||||||
|
- **GET** endpoints return the requested object(s) and status **OK (200)**
|
||||||
|
- **DELETE** endpoints return the status **No Content (204)** and no content either
|
||||||
|
- **POST** endpoints are used to **create** new objects (e.g. a User) and return the status **Created (201)** and the created object
|
||||||
|
- **PUT** endpoints are used to **add/assign** existing Objects (e.g. a user to a team) and return the status **No Content (204)** and no content either
|
||||||
|
- **PATCH** endpoints are used to **edit/change** an existing object and return the changed object and the status **OK (200)**
|
||||||
|
|
||||||
|
### Requirements for API routes
|
||||||
|
|
||||||
|
All parameters of endpoints changing/editing an object must be optional (except the ones to identify the object, which are required).
|
||||||
|
|
||||||
|
Endpoints returning lists must
|
||||||
|
|
||||||
|
- support pagination (`page` & `limit` options in query)
|
||||||
|
- set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
|
||||||
|
|
||||||
|
## Backports and Frontports
|
||||||
|
|
||||||
|
### What is backported?
|
||||||
|
|
||||||
|
We backport PRs given the following circumstances:
|
||||||
|
|
||||||
|
1. Feature freeze is active, but `<version>-rc0` has not been released yet. Here, we backport as much as possible. <!-- TODO: Is that our definition with the new backport bot? -->
|
||||||
|
2. `rc0` has been released. Here, we only backport bug- and security-fixes, and small enhancements. Large PRs such as refactors are not backported anymore. <!-- TODO: Is that our definition with the new backport bot? -->
|
||||||
|
3. We never backport new features.
|
||||||
|
4. We never backport breaking changes except when
|
||||||
|
1. The breaking change has no effect on the vast majority of users
|
||||||
|
2. The component triggering the breaking change is marked as experimental
|
||||||
|
|
||||||
|
### How to backport?
|
||||||
|
|
||||||
|
In the past, it was necessary to manually backport your PRs. \
|
||||||
|
Now, that's not a requirement anymore as our [backport bot](https://github.com/GiteaBot) tries to create backports automatically once the PR is merged when the PR
|
||||||
|
|
||||||
|
- does not have the label `backport/manual`
|
||||||
|
- has the label `backport/<version>`
|
||||||
|
|
||||||
|
The `backport/manual` label signifies either that you want to backport the change yourself, or that there were conflicts when backporting, thus you **must** do it yourself.
|
||||||
|
|
||||||
|
### Format of backport PRs
|
||||||
|
|
||||||
|
The title of backport PRs should be
|
||||||
|
|
||||||
|
```
|
||||||
|
<original PR title> (#<original pr number>)
|
||||||
|
```
|
||||||
|
|
||||||
|
The first two lines of the summary of the backporting PR should be
|
||||||
|
|
||||||
|
```
|
||||||
|
Backport #<original pr number>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
with the rest of the summary and labels matching the original PR.
|
||||||
|
|
||||||
|
### Frontports
|
||||||
|
|
||||||
|
Frontports behave exactly as described above for backports.
|
||||||
|
|
||||||
## Developer Certificate of Origin (DCO)
|
## Developer Certificate of Origin (DCO)
|
||||||
|
|
||||||
We consider the act of contributing to the code by submitting a Pull Request as the "Sign off" or agreement to the certifications and terms of the [DCO](DCO) and [MIT license](LICENSE). \
|
We consider the act of contributing to the code by submitting a Pull Request as the "Sign off" or agreement to the certifications and terms of the [DCO](DCO) and [MIT license](LICENSE). \
|
||||||
@@ -275,3 +459,148 @@ Signed-off-by: Joe Smith <joe.smith@email.com>
|
|||||||
If you set the `user.name` and `user.email` Git config options, you can add the line to the end of your commits automatically with `git commit -s`.
|
If you set the `user.name` and `user.email` Git config options, you can add the line to the end of your commits automatically with `git commit -s`.
|
||||||
|
|
||||||
We assume in good faith that the information you provide is legally binding.
|
We assume in good faith that the information you provide is legally binding.
|
||||||
|
|
||||||
|
## Release Cycle
|
||||||
|
|
||||||
|
We adopted a release schedule to streamline the process of working on, finishing, and issuing releases. \
|
||||||
|
The overall goal is to make a major release every three or four months, which breaks down into two or three months of general development followed by one month of testing and polishing known as the release freeze. \
|
||||||
|
All the feature pull requests should be
|
||||||
|
merged before feature freeze. All feature pull requests haven't been merged before this feature freeze will be moved to next milestone, please notice our feature freeze announcement on discord. And, during the frozen period, a corresponding
|
||||||
|
release branch is open for fixes backported from main branch. Release candidates
|
||||||
|
are made during this period for user testing to
|
||||||
|
obtain a final version that is maintained in this branch.
|
||||||
|
|
||||||
|
During a development cycle, we may also publish any necessary minor releases
|
||||||
|
for the previous version. For example, if the latest, published release is
|
||||||
|
v1.2, then minor changes for the previous release—e.g., v1.1.0 -> v1.1.1—are
|
||||||
|
still possible.
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
To make sure every PR is checked, we have [maintainers](MAINTAINERS). \
|
||||||
|
Every PR **must** be reviewed by at least two maintainers (or owners) before it can get merged. \
|
||||||
|
For refactoring PRs after a week and documentation only PRs, the approval of only one maintainer is enough. \
|
||||||
|
A maintainer should be a contributor of Gitea and contributed at least
|
||||||
|
4 accepted PRs. A contributor should apply as a maintainer in the
|
||||||
|
[Discord](https://discord.gg/Gitea) `#develop` channel. The team maintainers may invite the contributor. A maintainer
|
||||||
|
should spend some time on code reviews. If a maintainer has no
|
||||||
|
time to do that, they should apply to leave the maintainers team
|
||||||
|
and we will give them the honor of being a member of the [advisors
|
||||||
|
team](https://github.com/orgs/go-gitea/teams/advisors). Of course, if
|
||||||
|
an advisor has time to code review, we will gladly welcome them back
|
||||||
|
to the maintainers team. If a maintainer is inactive for more than 3
|
||||||
|
months and forgets to leave the maintainers team, the owners may move
|
||||||
|
him or her from the maintainers team to the advisors team.
|
||||||
|
For security reasons, Maintainers should use 2FA for their accounts and
|
||||||
|
if possible provide GPG signed commits.
|
||||||
|
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||||
|
https://help.github.com/articles/signing-commits-with-gpg/
|
||||||
|
|
||||||
|
Furthermore, any account with write access (like bots and TOC members) **must** use 2FA.
|
||||||
|
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||||
|
|
||||||
|
## Technical Oversight Committee (TOC)
|
||||||
|
|
||||||
|
At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions are elected as it has been over the past years, and the other three consist of appointed members from the Gitea company.
|
||||||
|
https://blog.gitea.com/quarterly-23q1/
|
||||||
|
|
||||||
|
### TOC election process
|
||||||
|
|
||||||
|
Any maintainer is eligible to be part of the community TOC if they are not associated with the Gitea company.
|
||||||
|
A maintainer can either nominate themselves, or can be nominated by other maintainers to be a candidate for the TOC election.
|
||||||
|
If you are nominated by someone else, you must first accept your nomination before the vote starts to be a candidate.
|
||||||
|
|
||||||
|
The TOC is elected for one year, the TOC election happens yearly.
|
||||||
|
After the announcement of the results of the TOC election, elected members have two weeks time to confirm or refuse the seat.
|
||||||
|
If an elected member does not answer within this timeframe, they are automatically assumed to refuse the seat.
|
||||||
|
Refusals result in the person with the next highest vote getting the same choice.
|
||||||
|
As long as seats are empty in the TOC, members of the previous TOC can fill them until an elected member accepts the seat.
|
||||||
|
|
||||||
|
If an elected member that accepts the seat does not have 2FA configured yet, they will be temporarily counted as `answer pending` until they manage to configure 2FA, thus leaving their seat empty for this duration.
|
||||||
|
|
||||||
|
### Current TOC members
|
||||||
|
|
||||||
|
- 2024-01-01 ~ 2024-12-31
|
||||||
|
- Company
|
||||||
|
- [Jason Song](https://gitea.com/wolfogre) <i@wolfogre.com>
|
||||||
|
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
|
||||||
|
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.com>
|
||||||
|
- Community
|
||||||
|
- [6543](https://gitea.com/6543) <6543@obermui.de>
|
||||||
|
- [delvh](https://gitea.com/delvh) <dev.lh@web.de>
|
||||||
|
- [John Olheiser](https://gitea.com/jolheiser) <john.olheiser@gmail.com>
|
||||||
|
|
||||||
|
### Previous TOC/owners members
|
||||||
|
|
||||||
|
Here's the history of the owners and the time they served:
|
||||||
|
|
||||||
|
- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||||
|
- [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017
|
||||||
|
- [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017
|
||||||
|
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801)
|
||||||
|
- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||||
|
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023
|
||||||
|
- [6543](https://gitea.com/6543) - 2023
|
||||||
|
- [John Olheiser](https://gitea.com/jolheiser) - 2023
|
||||||
|
- [Jason Song](https://gitea.com/wolfogre) - 2023
|
||||||
|
|
||||||
|
## Governance Compensation
|
||||||
|
|
||||||
|
Each member of the community elected TOC will be granted $500 each month as compensation for their work.
|
||||||
|
|
||||||
|
Furthermore, any community release manager for a specific release or LTS will be compensated $500 for the delivery of said release.
|
||||||
|
|
||||||
|
These funds will come from community sources like the OpenCollective rather than directly from the company.
|
||||||
|
Only non-company members are eligible for this compensation, and if a member of the community TOC takes the responsibility of release manager, they would only be compensated for their TOC duties.
|
||||||
|
Gitea Ltd employees are not eligible to receive any funds from the OpenCollective unless it is reimbursement for a purchase made for the Gitea project itself.
|
||||||
|
|
||||||
|
## TOC & Working groups
|
||||||
|
|
||||||
|
With Gitea covering many projects outside of the main repository, several groups will be created to help focus on specific areas instead of requiring maintainers to be a jack-of-all-trades. Maintainers are of course more than welcome to be part of multiple groups should they wish to contribute in multiple places.
|
||||||
|
|
||||||
|
The currently proposed groups are:
|
||||||
|
|
||||||
|
- **Core Group**: maintain the primary Gitea repository
|
||||||
|
- **Integration Group**: maintain the Gitea ecosystem's related tools, including go-sdk/tea/changelog/bots etc.
|
||||||
|
- **Documentation Group**: maintain related documents and repositories
|
||||||
|
- **Translation Group**: coordinate with translators and maintain translations
|
||||||
|
- **Security Group**: managed by TOC directly, members are decided by TOC, maintains security patches/responsible for security items
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
Each year a roadmap will be discussed with the entire Gitea maintainers team, and feedback will be solicited from various stakeholders.
|
||||||
|
TOC members need to review the roadmap every year and work together on the direction of the project.
|
||||||
|
|
||||||
|
When a vote is required for a proposal or other change, the vote of community elected TOC members count slightly more than the vote of company elected TOC members. With this approach, we both avoid ties and ensure that changes align with the mission statement and community opinion.
|
||||||
|
|
||||||
|
You can visit our roadmap on the wiki.
|
||||||
|
|
||||||
|
## Versions
|
||||||
|
|
||||||
|
Gitea has the `main` branch as a tip branch and has version branches
|
||||||
|
such as `release/v1.19`. `release/v1.19` is a release branch and we will
|
||||||
|
tag `v1.19.0` for binary download. If `v1.19.0` has bugs, we will accept
|
||||||
|
pull requests on the `release/v1.19` branch and publish a `v1.19.1` tag,
|
||||||
|
after bringing the bug fix also to the main branch.
|
||||||
|
|
||||||
|
Since the `main` branch is a tip version, if you wish to use Gitea
|
||||||
|
in production, please download the latest release tag version. All the
|
||||||
|
branches will be protected via GitHub, all the PRs to every branch must
|
||||||
|
be reviewed by two maintainers and must pass the automatic tests.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
- 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:
|
||||||
|
- 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`
|
||||||
|
- If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged.
|
||||||
|
- Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`.
|
||||||
|
- And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
||||||
|
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
||||||
|
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
||||||
|
- Verify all release assets were correctly published through CI on dl.gitea.com and GitHub releases. Once ACKed:
|
||||||
|
- bump the version of https://dl.gitea.com/gitea/version.json
|
||||||
|
- merge the blog post PR
|
||||||
|
- announce the release in discord `#announcements`
|
||||||
|
|||||||
62
Dockerfile
62
Dockerfile
@@ -1,15 +1,8 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# Build stage
|
||||||
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
|
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
|
||||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build
|
|
||||||
RUN apk --no-cache add build-base git nodejs pnpm
|
|
||||||
WORKDIR /src
|
|
||||||
COPY package.json pnpm-lock.yaml .npmrc ./
|
|
||||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile
|
|
||||||
COPY --exclude=.git/ . .
|
|
||||||
RUN make frontend
|
|
||||||
|
|
||||||
# Build backend for each target platform
|
ARG GOPROXY
|
||||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
ENV GOPROXY=${GOPROXY:-direct}
|
||||||
|
|
||||||
ARG GITEA_VERSION
|
ARG GITEA_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||||
@@ -19,32 +12,37 @@ ARG CGO_EXTRA_CFLAGS
|
|||||||
# Build deps
|
# Build deps
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
build-base \
|
build-base \
|
||||||
git
|
git \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Setup repo
|
||||||
|
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
# Use COPY instead of bind mount as read-only one breaks makefile state tracking and read-write one needs binary to be moved as it's discarded.
|
|
||||||
# ".git" directory is mounted separately later only for version data extraction.
|
|
||||||
COPY --exclude=.git/ . .
|
|
||||||
COPY --from=frontend-build /src/public/assets public/assets
|
|
||||||
|
|
||||||
# Build gitea, .git mount is required for version data
|
# Checkout version if set
|
||||||
RUN --mount=type=cache,target="/root/.cache/go-build" \
|
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||||
--mount=type=bind,source=".git/",target=".git/" \
|
&& make clean-all build
|
||||||
make backend
|
|
||||||
|
|
||||||
|
# Begin env-to-ini build
|
||||||
|
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||||
|
|
||||||
|
# Copy local files
|
||||||
COPY docker/root /tmp/local
|
COPY docker/root /tmp/local
|
||||||
|
|
||||||
# Set permissions for builds that made under windows which strips the executable bit from file
|
# Set permissions
|
||||||
RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||||
/tmp/local/usr/local/bin/* \
|
/tmp/local/usr/local/bin/gitea \
|
||||||
/tmp/local/etc/s6/gitea/* \
|
/tmp/local/etc/s6/gitea/* \
|
||||||
/tmp/local/etc/s6/openssh/* \
|
/tmp/local/etc/s6/openssh/* \
|
||||||
/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
|
||||||
|
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.23 AS gitea
|
FROM docker.io/library/alpine:3.22
|
||||||
|
LABEL maintainer="maintainers@gitea.io"
|
||||||
|
|
||||||
EXPOSE 22 3000
|
EXPOSE 22 3000
|
||||||
|
|
||||||
@@ -59,7 +57,8 @@ RUN apk --no-cache add \
|
|||||||
s6 \
|
s6 \
|
||||||
sqlite \
|
sqlite \
|
||||||
su-exec \
|
su-exec \
|
||||||
gnupg
|
gnupg \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
RUN addgroup \
|
RUN addgroup \
|
||||||
-S -g 1000 \
|
-S -g 1000 \
|
||||||
@@ -73,14 +72,15 @@ RUN addgroup \
|
|||||||
git && \
|
git && \
|
||||||
echo "git:*" | chpasswd -e
|
echo "git:*" | chpasswd -e
|
||||||
|
|
||||||
COPY --from=build-env /tmp/local /
|
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
|
||||||
|
|
||||||
ENV USER=git
|
ENV USER=git
|
||||||
ENV GITEA_CUSTOM=/data/gitea
|
ENV GITEA_CUSTOM=/data/gitea
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
# HINT: HEALTH-CHECK-ENDPOINT: don't use HEALTHCHECK, search this hint keyword for more information
|
|
||||||
ENTRYPOINT ["/usr/bin/entrypoint"]
|
ENTRYPOINT ["/usr/bin/entrypoint"]
|
||||||
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
|
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
|
||||||
|
|
||||||
|
COPY --from=build-env /tmp/local /
|
||||||
|
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,45 +1,46 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# Build stage
|
||||||
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
|
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
|
||||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build
|
|
||||||
RUN apk --no-cache add build-base git nodejs pnpm
|
|
||||||
WORKDIR /src
|
|
||||||
COPY package.json pnpm-lock.yaml .npmrc ./
|
|
||||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile
|
|
||||||
COPY --exclude=.git/ . .
|
|
||||||
RUN make frontend
|
|
||||||
|
|
||||||
# Build backend for each target platform
|
ARG GOPROXY
|
||||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
ENV GOPROXY=${GOPROXY:-direct}
|
||||||
|
|
||||||
ARG GITEA_VERSION
|
ARG GITEA_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||||
ENV TAGS="bindata timetzdata $TAGS"
|
ENV TAGS="bindata timetzdata $TAGS"
|
||||||
ARG CGO_EXTRA_CFLAGS
|
ARG CGO_EXTRA_CFLAGS
|
||||||
|
|
||||||
# Build deps
|
#Build deps
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
build-base \
|
build-base \
|
||||||
git
|
git \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Setup repo
|
||||||
|
COPY . ${GOPATH}/src/code.gitea.io/gitea
|
||||||
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
# See the comments in Dockerfile
|
|
||||||
COPY --exclude=.git/ . .
|
|
||||||
COPY --from=frontend-build /src/public/assets public/assets
|
|
||||||
|
|
||||||
# Build gitea, .git mount is required for version data
|
# Checkout version if set
|
||||||
RUN --mount=type=cache,target="/root/.cache/go-build" \
|
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||||
--mount=type=bind,source=".git/",target=".git/" \
|
&& make clean-all build
|
||||||
make backend
|
|
||||||
|
|
||||||
|
# Begin env-to-ini build
|
||||||
|
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||||
|
|
||||||
|
# Copy local files
|
||||||
COPY docker/rootless /tmp/local
|
COPY docker/rootless /tmp/local
|
||||||
|
|
||||||
# Set permissions for builds that made under windows which strips the executable bit from file
|
# Set permissions
|
||||||
RUN chmod 755 /tmp/local/usr/local/bin/* \
|
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||||
/go/src/code.gitea.io/gitea/gitea
|
/tmp/local/usr/local/bin/docker-setup.sh \
|
||||||
|
/tmp/local/usr/local/bin/gitea \
|
||||||
|
/go/src/code.gitea.io/gitea/gitea \
|
||||||
|
/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.23 AS gitea-rootless
|
FROM docker.io/library/alpine:3.22
|
||||||
|
LABEL maintainer="maintainers@gitea.io"
|
||||||
|
|
||||||
EXPOSE 2222 3000
|
EXPOSE 2222 3000
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ RUN apk --no-cache add \
|
|||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
gnupg \
|
gnupg \
|
||||||
openssh-keygen
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
RUN addgroup \
|
RUN addgroup \
|
||||||
-S -g 1000 \
|
-S -g 1000 \
|
||||||
@@ -69,6 +70,8 @@ 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 /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
|
||||||
@@ -83,6 +86,5 @@ ENV HOME="/var/lib/gitea/git"
|
|||||||
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
||||||
WORKDIR /var/lib/gitea
|
WORKDIR /var/lib/gitea
|
||||||
|
|
||||||
# HINT: HEALTH-CHECK-ENDPOINT: don't use HEALTHCHECK, search this hint keyword for more information
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||||
CMD []
|
CMD []
|
||||||
|
|||||||
@@ -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,6 +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)
|
|
||||||
Nicolas <bircni@icloud.com> (@bircni)
|
|
||||||
428
Makefile
428
Makefile
@@ -1,26 +1,42 @@
|
|||||||
|
ifeq ($(USE_REPO_TEST_DIR),1)
|
||||||
|
|
||||||
|
# This rule replaces the whole Makefile when we're trying to use /tmp repository temporary files
|
||||||
|
location = $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
|
||||||
|
self := $(location)
|
||||||
|
|
||||||
|
%:
|
||||||
|
@tmpdir=`mktemp --tmpdir -d` ; \
|
||||||
|
echo Using temporary directory $$tmpdir for test repositories ; \
|
||||||
|
USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \
|
||||||
|
STATUS=$$? ; rm -r "$$tmpdir" ; exit $$STATUS
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
# This is the "normal" part of the Makefile
|
||||||
|
|
||||||
DIST := dist
|
DIST := dist
|
||||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||||
|
IMPORT := code.gitea.io/gitea
|
||||||
# By default use go's 1.25 experimental json v2 library when building
|
|
||||||
# TODO: remove when no longer experimental
|
|
||||||
export GOEXPERIMENT ?= jsonv2
|
|
||||||
|
|
||||||
GO ?= go
|
GO ?= go
|
||||||
SHASUM ?= shasum -a 256
|
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
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
|
||||||
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
||||||
|
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1
|
||||||
|
|
||||||
DOCKER_IMAGE ?= gitea/gitea
|
DOCKER_IMAGE ?= gitea/gitea
|
||||||
DOCKER_TAG ?= latest
|
DOCKER_TAG ?= latest
|
||||||
@@ -31,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)
|
||||||
@@ -52,11 +57,9 @@ endif
|
|||||||
ifeq ($(IS_WINDOWS),yes)
|
ifeq ($(IS_WINDOWS),yes)
|
||||||
GOFLAGS := -v -buildmode=exe
|
GOFLAGS := -v -buildmode=exe
|
||||||
EXECUTABLE ?= gitea.exe
|
EXECUTABLE ?= gitea.exe
|
||||||
EXECUTABLE_E2E ?= gitea-e2e.exe
|
|
||||||
else
|
else
|
||||||
GOFLAGS := -v
|
GOFLAGS := -v
|
||||||
EXECUTABLE ?= gitea
|
EXECUTABLE ?= gitea
|
||||||
EXECUTABLE_E2E ?= gitea-e2e
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
||||||
@@ -67,6 +70,7 @@ endif
|
|||||||
|
|
||||||
EXTRA_GOFLAGS ?=
|
EXTRA_GOFLAGS ?=
|
||||||
|
|
||||||
|
MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1)
|
||||||
MAKE_EVIDENCE_DIR := .make_evidence
|
MAKE_EVIDENCE_DIR := .make_evidence
|
||||||
|
|
||||||
GOTESTFLAGS ?=
|
GOTESTFLAGS ?=
|
||||||
@@ -76,6 +80,7 @@ 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)
|
||||||
@@ -103,20 +108,20 @@ ifeq ($(VERSION),main)
|
|||||||
VERSION := main-nightly
|
VERSION := main-nightly
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
|
|
||||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
|
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
|
||||||
|
|
||||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration,$(shell $(GO) list ./... | grep -v /vendor/))
|
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||||
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
||||||
|
|
||||||
FRONTEND_SOURCES := $(shell find web_src/js web_src/css -type f)
|
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
||||||
FRONTEND_CONFIGS := vite.config.ts tailwind.config.ts
|
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
||||||
FRONTEND_DEST := public/assets/.vite/manifest.json
|
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
||||||
FRONTEND_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/.vite
|
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
|
||||||
FRONTEND_DEV_LOG_LEVEL ?= warn
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -124,6 +129,7 @@ SVG_DEST_DIR := public/assets/img/svg
|
|||||||
|
|
||||||
AIR_TMP_DIR := .air
|
AIR_TMP_DIR := .air
|
||||||
|
|
||||||
|
GO_LICENSE_TMP_DIR := .go-licenses
|
||||||
GO_LICENSE_FILE := assets/go-licenses.json
|
GO_LICENSE_FILE := assets/go-licenses.json
|
||||||
|
|
||||||
TAGS ?=
|
TAGS ?=
|
||||||
@@ -132,21 +138,30 @@ TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
|
|||||||
|
|
||||||
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
|
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
|
||||||
|
|
||||||
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
|
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR)
|
||||||
|
|
||||||
GO_DIRS := build cmd models modules routers services tests tools
|
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.json .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml))
|
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.json
|
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)
|
||||||
|
|
||||||
ESLINT_CONCURRENCY ?= 2
|
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
|
||||||
|
ifdef DEPS_PLAYWRIGHT
|
||||||
|
PLAYWRIGHT_FLAGS += --with-deps
|
||||||
|
endif
|
||||||
|
|
||||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||||
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
|
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
|
||||||
@@ -167,20 +182,26 @@ TEST_MSSQL_DBNAME ?= gitea
|
|||||||
TEST_MSSQL_USERNAME ?= sa
|
TEST_MSSQL_USERNAME ?= sa
|
||||||
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
|
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
|
||||||
|
|
||||||
# Include local Makefile
|
|
||||||
# Makefile.local is listed in .gitignore
|
|
||||||
sinclude Makefile.local
|
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help: Makefile ## print Makefile help information.
|
help: Makefile ## print Makefile help information.
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST)
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST)
|
||||||
@printf " \033[36m%-46s\033[0m %s\n" "test-e2e" "test end to end using playwright"
|
@printf " \033[36m%-46s\033[0m %s\n" "test-e2e[#TestSpecificName]" "test end to end using playwright"
|
||||||
@printf " \033[36m%-46s\033[0m %s\n" "test[#TestSpecificName]" "run unit test"
|
@printf " \033[36m%-46s\033[0m %s\n" "test[#TestSpecificName]" "run unit test"
|
||||||
@printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite"
|
@printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite"
|
||||||
|
|
||||||
|
.PHONY: go-check
|
||||||
|
go-check:
|
||||||
|
$(eval MIN_GO_VERSION_STR := $(shell grep -Eo '^go\s+[0-9]+\.[0-9]+' go.mod | cut -d' ' -f2))
|
||||||
|
$(eval MIN_GO_VERSION := $(shell printf "%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' ')))
|
||||||
|
$(eval GO_VERSION := $(shell printf "%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9]+' | tr '.' ' ');))
|
||||||
|
@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
|
||||||
|
echo "Gitea requires Go $(MIN_GO_VERSION_STR) or greater to build. You can get it at https://go.dev/dl/"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
.PHONY: git-check
|
.PHONY: git-check
|
||||||
git-check:
|
git-check:
|
||||||
@if git lfs >/dev/null 2>&1 ; then : ; else \
|
@if git lfs >/dev/null 2>&1 ; then : ; else \
|
||||||
@@ -188,24 +209,36 @@ git-check:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
.PHONY: 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 := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
|
||||||
|
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
|
||||||
|
$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
|
||||||
|
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
|
||||||
|
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/"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
.PHONY: clean-all
|
.PHONY: clean-all
|
||||||
clean-all: clean ## delete backend, frontend and integration files
|
clean-all: clean ## delete backend, frontend and integration files
|
||||||
rm -rf $(FRONTEND_DEST_ENTRIES) node_modules
|
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: ## delete backend and integration files
|
clean: ## delete backend and integration files
|
||||||
rm -rf $(EXECUTABLE) $(EXECUTABLE_E2E) $(DIST) $(BINDATA_DEST_WILDCARD) \
|
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
|
||||||
integrations*.test \
|
integrations*.test \
|
||||||
|
e2e*.test \
|
||||||
tests/integration/gitea-integration-* \
|
tests/integration/gitea-integration-* \
|
||||||
tests/integration/indexers-* \
|
tests/integration/indexers-* \
|
||||||
tests/sqlite.ini tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \
|
tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \
|
||||||
tests/e2e/gitea-e2e-*/ \
|
tests/e2e/gitea-e2e-*/ \
|
||||||
tests/e2e/indexers-*/ \
|
tests/e2e/indexers-*/ \
|
||||||
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
|
||||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) fmt
|
@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
|
||||||
@# whitespace before it
|
@# whitespace before it
|
||||||
@@ -235,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
|
||||||
@@ -277,48 +310,44 @@ lint-frontend: lint-js lint-css ## lint frontend files
|
|||||||
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
|
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
|
||||||
|
|
||||||
.PHONY: lint-backend
|
.PHONY: lint-backend
|
||||||
lint-backend: lint-go lint-editorconfig ## lint backend files
|
lint-backend: lint-go lint-go-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files
|
||||||
|
|
||||||
.PHONY: lint-backend-fix
|
.PHONY: lint-backend-fix
|
||||||
lint-backend-fix: lint-go-fix lint-editorconfig ## lint backend files and fix issues
|
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
|
||||||
|
|
||||||
.PHONY: lint-js
|
.PHONY: lint-js
|
||||||
lint-js: node_modules ## lint js and ts files
|
lint-js: node_modules ## lint js files
|
||||||
pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES)
|
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
|
||||||
pnpm exec vue-tsc
|
npx vue-tsc
|
||||||
|
|
||||||
.PHONY: lint-js-fix
|
.PHONY: lint-js-fix
|
||||||
lint-js-fix: node_modules ## lint js and ts files and fix issues
|
lint-js-fix: node_modules ## lint js files and fix issues
|
||||||
pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES) --fix
|
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
pnpm exec markdownlint *.md
|
npx markdownlint *.md
|
||||||
|
|
||||||
.PHONY: lint-md-fix
|
|
||||||
lint-md-fix: node_modules ## lint markdown files and fix issues
|
|
||||||
pnpm exec markdownlint --fix *.md
|
|
||||||
|
|
||||||
.PHONY: lint-spell
|
.PHONY: lint-spell
|
||||||
lint-spell: ## lint spelling
|
lint-spell: ## lint spelling
|
||||||
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error
|
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES)
|
||||||
|
|
||||||
.PHONY: lint-spell-fix
|
.PHONY: lint-spell-fix
|
||||||
lint-spell-fix: ## lint spelling and fix issues
|
lint-spell-fix: ## lint spelling and fix issues
|
||||||
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w
|
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES)
|
||||||
|
|
||||||
.PHONY: lint-go
|
.PHONY: lint-go
|
||||||
lint-go: ## lint go files
|
lint-go: ## lint go files
|
||||||
@@ -335,6 +364,17 @@ lint-go-windows:
|
|||||||
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
|
.PHONY: lint-go-gitea-vet
|
||||||
|
lint-go-gitea-vet: ## lint go files with gitea-vet
|
||||||
|
@echo "Running gitea-vet..."
|
||||||
|
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
|
||||||
|
@$(GO) vet -vettool=gitea-vet ./...
|
||||||
|
|
||||||
|
.PHONY: lint-go-gopls
|
||||||
|
lint-go-gopls: ## lint go files with gopls
|
||||||
|
@echo "Running gopls check..."
|
||||||
|
@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:
|
||||||
@echo "Running editorconfig check..."
|
@echo "Running editorconfig check..."
|
||||||
@@ -346,31 +386,24 @@ 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: lint-json
|
|
||||||
lint-json: node_modules ## lint json files
|
|
||||||
pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY)
|
|
||||||
|
|
||||||
.PHONY: lint-json-fix
|
|
||||||
lint-json-fix: node_modules ## lint and fix json files
|
|
||||||
pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) --fix
|
|
||||||
|
|
||||||
.PHONY: watch
|
.PHONY: watch
|
||||||
watch: ## watch everything and continuously rebuild
|
watch: ## watch everything and continuously rebuild
|
||||||
@bash tools/watch.sh
|
@bash tools/watch.sh
|
||||||
|
|
||||||
.PHONY: watch-frontend
|
.PHONY: watch-frontend
|
||||||
watch-frontend: node_modules ## start vite dev server for frontend
|
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
|
||||||
NODE_ENV=development pnpm exec vite --logLevel $(FRONTEND_DEV_LOG_LEVEL)
|
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
||||||
|
NODE_ENV=development npx webpack --watch --progress
|
||||||
|
|
||||||
.PHONY: watch-backend
|
.PHONY: watch-backend
|
||||||
watch-backend: ## watch backend files and continuously rebuild
|
watch-backend: go-check ## watch backend files and continuously rebuild
|
||||||
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
@@ -383,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
|
||||||
pnpm exec vitest
|
npx vitest
|
||||||
|
|
||||||
.PHONY: test-check
|
.PHONY: test-check
|
||||||
test-check:
|
test-check:
|
||||||
@@ -406,7 +439,7 @@ test\#%:
|
|||||||
coverage:
|
coverage:
|
||||||
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
|
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
|
||||||
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
|
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
|
||||||
$(GO) run tools/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
|
$(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
|
||||||
|
|
||||||
.PHONY: unit-test-coverage
|
.PHONY: unit-test-coverage
|
||||||
unit-test-coverage:
|
unit-test-coverage:
|
||||||
@@ -436,20 +469,25 @@ tidy-check: tidy
|
|||||||
go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses
|
go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses
|
||||||
|
|
||||||
$(GO_LICENSE_FILE): go.mod go.sum
|
$(GO_LICENSE_FILE): go.mod go.sum
|
||||||
GO=$(GO) $(GO) run build/generate-go-licenses.go $(GO_LICENSE_FILE)
|
@rm -rf $(GO_LICENSE_FILE)
|
||||||
|
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||||
|
-GOOS=linux CGO_ENABLED=1 go-licenses save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
|
||||||
|
$(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE)
|
||||||
|
@rm -rf $(GO_LICENSE_TMP_DIR)
|
||||||
|
|
||||||
generate-ini-sqlite:
|
generate-ini-sqlite:
|
||||||
sed -e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-sqlite|g' \
|
sed -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||||
|
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||||
tests/sqlite.ini.tmpl > tests/sqlite.ini
|
tests/sqlite.ini.tmpl > tests/sqlite.ini
|
||||||
|
|
||||||
.PHONY: test-sqlite
|
.PHONY: test-sqlite
|
||||||
test-sqlite: integrations.sqlite.test generate-ini-sqlite
|
test-sqlite: integrations.sqlite.test generate-ini-sqlite
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test
|
||||||
|
|
||||||
.PHONY: test-sqlite\#%
|
.PHONY: test-sqlite\#%
|
||||||
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
|
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-sqlite-migration
|
.PHONY: test-sqlite-migration
|
||||||
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
|
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
|
||||||
@@ -459,17 +497,18 @@ generate-ini-mysql:
|
|||||||
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
|
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
|
||||||
-e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \
|
-e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \
|
||||||
-e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \
|
-e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \
|
||||||
-e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-mysql|g' \
|
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||||
|
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||||
tests/mysql.ini.tmpl > tests/mysql.ini
|
tests/mysql.ini.tmpl > tests/mysql.ini
|
||||||
|
|
||||||
.PHONY: test-mysql
|
.PHONY: test-mysql
|
||||||
test-mysql: integrations.mysql.test generate-ini-mysql
|
test-mysql: integrations.mysql.test generate-ini-mysql
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test
|
||||||
|
|
||||||
.PHONY: test-mysql\#%
|
.PHONY: test-mysql\#%
|
||||||
test-mysql\#%: integrations.mysql.test generate-ini-mysql
|
test-mysql\#%: integrations.mysql.test generate-ini-mysql
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-mysql-migration
|
.PHONY: test-mysql-migration
|
||||||
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test
|
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test
|
||||||
@@ -481,17 +520,18 @@ generate-ini-pgsql:
|
|||||||
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
|
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
|
||||||
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \
|
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \
|
||||||
-e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \
|
-e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \
|
||||||
-e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-pgsql|g' \
|
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||||
|
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||||
tests/pgsql.ini.tmpl > tests/pgsql.ini
|
tests/pgsql.ini.tmpl > tests/pgsql.ini
|
||||||
|
|
||||||
.PHONY: test-pgsql
|
.PHONY: test-pgsql
|
||||||
test-pgsql: integrations.pgsql.test generate-ini-pgsql
|
test-pgsql: integrations.pgsql.test generate-ini-pgsql
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test
|
||||||
|
|
||||||
.PHONY: test-pgsql\#%
|
.PHONY: test-pgsql\#%
|
||||||
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
|
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-pgsql-migration
|
.PHONY: test-pgsql-migration
|
||||||
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test
|
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test
|
||||||
@@ -501,53 +541,89 @@ generate-ini-mssql:
|
|||||||
-e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \
|
-e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \
|
||||||
-e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \
|
-e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \
|
||||||
-e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \
|
-e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \
|
||||||
-e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-mssql|g' \
|
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||||
|
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||||
tests/mssql.ini.tmpl > tests/mssql.ini
|
tests/mssql.ini.tmpl > tests/mssql.ini
|
||||||
|
|
||||||
.PHONY: test-mssql
|
.PHONY: test-mssql
|
||||||
test-mssql: integrations.mssql.test generate-ini-mssql
|
test-mssql: integrations.mssql.test generate-ini-mssql
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test
|
||||||
|
|
||||||
.PHONY: test-mssql\#%
|
.PHONY: test-mssql\#%
|
||||||
test-mssql\#%: integrations.mssql.test generate-ini-mssql
|
test-mssql\#%: integrations.mssql.test generate-ini-mssql
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
.PHONY: test-mssql-migration
|
.PHONY: test-mssql-migration
|
||||||
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
|
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
|
||||||
|
|
||||||
.PHONY: playwright
|
.PHONY: playwright
|
||||||
playwright: deps-frontend
|
playwright: deps-frontend
|
||||||
@# on GitHub Actions VMs, playwright's system deps are pre-installed
|
npx playwright install $(PLAYWRIGHT_FLAGS)
|
||||||
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS)
|
|
||||||
|
.PHONY: test-e2e%
|
||||||
|
test-e2e%: TEST_TYPE ?= e2e
|
||||||
|
# Clear display env variable. Otherwise, chromium tests can fail.
|
||||||
|
DISPLAY=
|
||||||
|
|
||||||
.PHONY: test-e2e
|
.PHONY: test-e2e
|
||||||
test-e2e: playwright $(EXECUTABLE_E2E)
|
test-e2e: test-e2e-sqlite
|
||||||
@EXECUTABLE=$(EXECUTABLE_E2E) ./tools/test-e2e.sh $(GITEA_TEST_E2E_FLAGS)
|
|
||||||
|
.PHONY: test-e2e-sqlite
|
||||||
|
test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test
|
||||||
|
|
||||||
|
.PHONY: test-e2e-sqlite\#%
|
||||||
|
test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e/$*
|
||||||
|
|
||||||
|
.PHONY: test-e2e-mysql
|
||||||
|
test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test
|
||||||
|
|
||||||
|
.PHONY: test-e2e-mysql\#%
|
||||||
|
test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e/$*
|
||||||
|
|
||||||
|
.PHONY: test-e2e-pgsql
|
||||||
|
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test
|
||||||
|
|
||||||
|
.PHONY: test-e2e-pgsql\#%
|
||||||
|
test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e/$*
|
||||||
|
|
||||||
|
.PHONY: test-e2e-mssql
|
||||||
|
test-e2e-mssql: playwright e2e.mssql.test generate-ini-mssql
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test
|
||||||
|
|
||||||
|
.PHONY: test-e2e-mssql\#%
|
||||||
|
test-e2e-mssql\#%: playwright e2e.mssql.test generate-ini-mssql
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e/$*
|
||||||
|
|
||||||
.PHONY: bench-sqlite
|
.PHONY: bench-sqlite
|
||||||
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
|
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: bench-mysql
|
.PHONY: bench-mysql
|
||||||
bench-mysql: integrations.mysql.test generate-ini-mysql
|
bench-mysql: integrations.mysql.test generate-ini-mysql
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: bench-mssql
|
.PHONY: bench-mssql
|
||||||
bench-mssql: integrations.mssql.test generate-ini-mssql
|
bench-mssql: integrations.mssql.test generate-ini-mssql
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: bench-pgsql
|
.PHONY: bench-pgsql
|
||||||
bench-pgsql: integrations.pgsql.test generate-ini-pgsql
|
bench-pgsql: integrations.pgsql.test generate-ini-pgsql
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||||
|
|
||||||
.PHONY: integration-test-coverage
|
.PHONY: integration-test-coverage
|
||||||
integration-test-coverage: integrations.cover.test generate-ini-mysql
|
integration-test-coverage: integrations.cover.test generate-ini-mysql
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
||||||
|
|
||||||
.PHONY: integration-test-coverage-sqlite
|
.PHONY: integration-test-coverage-sqlite
|
||||||
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
|
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
||||||
|
|
||||||
integrations.mysql.test: git-check $(GO_SOURCES)
|
integrations.mysql.test: git-check $(GO_SOURCES)
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
|
||||||
@@ -570,54 +646,66 @@ integrations.cover.sqlite.test: git-check $(GO_SOURCES)
|
|||||||
.PHONY: migrations.mysql.test
|
.PHONY: migrations.mysql.test
|
||||||
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
|
||||||
GITEA_TEST_CONF=tests/mysql.ini ./migrations.mysql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test
|
||||||
|
|
||||||
.PHONY: migrations.pgsql.test
|
.PHONY: migrations.pgsql.test
|
||||||
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini ./migrations.pgsql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test
|
||||||
|
|
||||||
.PHONY: migrations.mssql.test
|
.PHONY: migrations.mssql.test
|
||||||
migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test
|
||||||
GITEA_TEST_CONF=tests/mssql.ini ./migrations.mssql.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./migrations.mssql.test
|
||||||
|
|
||||||
.PHONY: migrations.sqlite.test
|
.PHONY: migrations.sqlite.test
|
||||||
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini ./migrations.sqlite.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./migrations.sqlite.test
|
||||||
|
|
||||||
.PHONY: migrations.individual.mysql.test
|
.PHONY: migrations.individual.mysql.test
|
||||||
migrations.individual.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
migrations.individual.mysql.test: $(GO_SOURCES)
|
||||||
GITEA_TEST_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test\#%
|
.PHONY: migrations.individual.sqlite.test\#%
|
||||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||||
|
|
||||||
.PHONY: migrations.individual.pgsql.test
|
.PHONY: migrations.individual.pgsql.test
|
||||||
migrations.individual.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: migrations.individual.pgsql.test\#%
|
.PHONY: migrations.individual.pgsql.test\#%
|
||||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||||
GITEA_TEST_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||||
|
|
||||||
.PHONY: migrations.individual.mssql.test
|
.PHONY: migrations.individual.mssql.test
|
||||||
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
||||||
GITEA_TEST_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: migrations.individual.mssql.test\#%
|
.PHONY: migrations.individual.mssql.test\#%
|
||||||
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
||||||
GITEA_TEST_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test
|
.PHONY: migrations.individual.sqlite.test
|
||||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: migrations.individual.sqlite.test\#%
|
.PHONY: migrations.individual.sqlite.test\#%
|
||||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||||
GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||||
|
|
||||||
|
e2e.mysql.test: $(GO_SOURCES)
|
||||||
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test
|
||||||
|
|
||||||
|
e2e.pgsql.test: $(GO_SOURCES)
|
||||||
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test
|
||||||
|
|
||||||
|
e2e.mssql.test: $(GO_SOURCES)
|
||||||
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mssql.test
|
||||||
|
|
||||||
|
e2e.sqlite.test: $(GO_SOURCES)
|
||||||
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
check: test
|
check: test
|
||||||
@@ -630,10 +718,10 @@ install: $(wildcard *.go)
|
|||||||
build: frontend backend ## build everything
|
build: frontend backend ## build everything
|
||||||
|
|
||||||
.PHONY: frontend
|
.PHONY: frontend
|
||||||
frontend: $(FRONTEND_DEST) ## build frontend files
|
frontend: $(WEBPACK_DEST) ## build frontend files
|
||||||
|
|
||||||
.PHONY: backend
|
.PHONY: backend
|
||||||
backend: generate-backend $(EXECUTABLE) ## build backend files
|
backend: go-check generate-backend $(EXECUTABLE) ## build backend files
|
||||||
|
|
||||||
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
|
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
|
||||||
.PHONY: generate
|
.PHONY: generate
|
||||||
@@ -649,16 +737,10 @@ generate-go: $(TAGS_PREREQ)
|
|||||||
|
|
||||||
.PHONY: security-check
|
.PHONY: security-check
|
||||||
security-check:
|
security-check:
|
||||||
GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./... || true
|
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 does not 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 $@
|
|
||||||
|
|
||||||
$(EXECUTABLE_E2E): $(GO_SOURCES) $(FRONTEND_DEST)
|
|
||||||
CGO_ENABLED=1 $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TEST_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
|
||||||
@@ -724,61 +806,59 @@ deps-backend: ## install backend dependencies
|
|||||||
deps-tools: ## install tool dependencies
|
deps-tools: ## install tool dependencies
|
||||||
$(GO) install $(AIR_PACKAGE) & \
|
$(GO) install $(AIR_PACKAGE) & \
|
||||||
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
||||||
|
$(GO) install $(GOFUMPT_PACKAGE) & \
|
||||||
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \
|
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \
|
||||||
$(GO) install $(GXZ_PACKAGE) & \
|
$(GO) install $(GXZ_PACKAGE) & \
|
||||||
$(GO) install $(MISSPELL_PACKAGE) & \
|
$(GO) install $(MISSPELL_PACKAGE) & \
|
||||||
$(GO) install $(SWAGGER_PACKAGE) & \
|
$(GO) install $(SWAGGER_PACKAGE) & \
|
||||||
$(GO) install $(XGO_PACKAGE) & \
|
$(GO) install $(XGO_PACKAGE) & \
|
||||||
|
$(GO) install $(GO_LICENSES_PACKAGE) & \
|
||||||
$(GO) install $(GOVULNCHECK_PACKAGE) & \
|
$(GO) install $(GOVULNCHECK_PACKAGE) & \
|
||||||
$(GO) install $(ACTIONLINT_PACKAGE) & \
|
$(GO) install $(ACTIONLINT_PACKAGE) & \
|
||||||
|
$(GO) install $(GOPLS_PACKAGE) & \
|
||||||
wait
|
wait
|
||||||
|
|
||||||
node_modules: pnpm-lock.yaml
|
node_modules: package-lock.json
|
||||||
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
|
||||||
update: update-go update-js update-py ## update dependencies
|
update: update-js update-py ## update js and py dependencies
|
||||||
|
|
||||||
.PHONY: update-go
|
|
||||||
update-go: ## update go dependencies
|
|
||||||
$(GO) get -u ./...
|
|
||||||
$(MAKE) tidy
|
|
||||||
|
|
||||||
.PHONY: update-js
|
.PHONY: update-js
|
||||||
update-js: node_modules ## update js dependencies
|
update-js: node-check | node_modules ## update js dependencies
|
||||||
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
|
||||||
pnpm install
|
npm install --package-lock
|
||||||
pnpm exec nolyfill install
|
npx nolyfill install
|
||||||
pnpm install
|
npm install --package-lock
|
||||||
@touch node_modules
|
@touch node_modules
|
||||||
|
|
||||||
.PHONY: update-py
|
.PHONY: update-py
|
||||||
update-py: node_modules ## update py dependencies
|
update-py: node-check | node_modules ## update py dependencies
|
||||||
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: vite
|
.PHONY: webpack
|
||||||
vite: $(FRONTEND_DEST) ## build vite files
|
webpack: $(WEBPACK_DEST) ## build webpack files
|
||||||
|
|
||||||
$(FRONTEND_DEST): $(FRONTEND_SOURCES) $(FRONTEND_CONFIGS) pnpm-lock.yaml
|
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
|
||||||
@$(MAKE) -s node_modules
|
@$(MAKE) -s node-check node_modules
|
||||||
@rm -rf $(FRONTEND_DEST_ENTRIES)
|
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
||||||
@echo "Running vite build..."
|
@echo "Running webpack..."
|
||||||
@pnpm exec vite build
|
@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
|
||||||
@touch $(FRONTEND_DEST)
|
@touch $(WEBPACK_DEST)
|
||||||
|
|
||||||
.PHONY: svg
|
.PHONY: svg
|
||||||
svg: 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
|
||||||
@@ -792,22 +872,33 @@ svg-check: svg
|
|||||||
|
|
||||||
.PHONY: lockfile-check
|
.PHONY: lockfile-check
|
||||||
lockfile-check:
|
lockfile-check:
|
||||||
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
|
||||||
|
|
||||||
|
.PHONY: update-translations
|
||||||
|
update-translations:
|
||||||
|
mkdir -p ./translations
|
||||||
|
cd ./translations && curl -L https://crowdin.com/download/project/gitea.zip > gitea.zip && unzip gitea.zip
|
||||||
|
rm ./translations/gitea.zip
|
||||||
|
$(SED_INPLACE) -e 's/="/=/g' -e 's/"$$//g' ./translations/*.ini
|
||||||
|
$(SED_INPLACE) -e 's/\\"/"/g' ./translations/*.ini
|
||||||
|
mv ./translations/*.ini ./options/locale/
|
||||||
|
rmdir ./translations
|
||||||
|
|
||||||
.PHONY: generate-gitignore
|
.PHONY: generate-gitignore
|
||||||
generate-gitignore: ## update gitignore files
|
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
|
||||||
@@ -822,6 +913,9 @@ docker:
|
|||||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||||
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
||||||
|
|
||||||
|
# This endif closes the if at the top of the file
|
||||||
|
endif
|
||||||
|
|
||||||
# Disable parallel execution because it would break some targets that don't
|
# Disable parallel execution because it would break some targets that don't
|
||||||
# specify exact dependencies like 'backend' which does currently not depend
|
# specify exact dependencies like 'backend' which does currently not depend
|
||||||
# on 'frontend' to enable Node.js-less builds from source tarballs.
|
# on 'frontend' to enable Node.js-less builds from source tarballs.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||||
|
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||||
[](https://translate.gitea.com "Crowdin")
|
[](https://translate.gitea.com "Crowdin")
|
||||||
|
|
||||||
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md)
|
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md)
|
||||||
@@ -51,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.
|
||||||
|
|
||||||
@@ -79,9 +80,9 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request
|
|||||||
|
|
||||||
[](https://translate.gitea.com)
|
[](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).
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||||
|
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||||
[](https://translate.gitea.com "Crowdin")
|
[](https://translate.gitea.com "Crowdin")
|
||||||
|
|
||||||
[English](./README.md) | [繁體中文](./README.zh-tw.md)
|
[English](./README.md) | [繁體中文](./README.zh-tw.md)
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
`build` 目标分为两个子目标:
|
`build` 目标分为两个子目标:
|
||||||
|
|
||||||
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
|
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
|
||||||
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本以及 [pnpm](https://pnpm.io/installation)。
|
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
|
||||||
|
|
||||||
需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。
|
需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||||
|
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||||
[](https://translate.gitea.com "Crowdin")
|
[](https://translate.gitea.com "Crowdin")
|
||||||
|
|
||||||
[English](./README.md) | [简体中文](./README.zh-cn.md)
|
[English](./README.md) | [简体中文](./README.zh-cn.md)
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
`build` 目標分為兩個子目標:
|
`build` 目標分為兩個子目標:
|
||||||
|
|
||||||
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
|
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
|
||||||
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本以及 [pnpm](https://pnpm.io/installation)。
|
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
|
||||||
|
|
||||||
需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。
|
需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。
|
||||||
|
|
||||||
|
|||||||
54
SECURITY.md
54
SECURITY.md
@@ -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-----
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
11484
assets/emoji.json
generated
11484
assets/emoji.json
generated
File diff suppressed because one or more lines are too long
382
assets/go-licenses.json
generated
382
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
23
build.go
Normal file
23
build.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//go:build vendor
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Libraries that are included to vendor utilities used during build.
|
||||||
|
// These libraries will not be included in a normal compilation.
|
||||||
|
|
||||||
|
import (
|
||||||
|
// for embed
|
||||||
|
_ "github.com/shurcooL/vfsgen"
|
||||||
|
|
||||||
|
// for cover merge
|
||||||
|
_ "golang.org/x/tools/cover"
|
||||||
|
|
||||||
|
// for vet
|
||||||
|
_ "code.gitea.io/gitea-vet"
|
||||||
|
|
||||||
|
// for swagger
|
||||||
|
_ "github.com/go-swagger/go-swagger/cmd/swagger"
|
||||||
|
)
|
||||||
115
build/backport-locales.go
Normal file
115
build/backport-locales.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
println("usage: backport-locales <to-ref>")
|
||||||
|
println("eg: backport-locales release/v1.19")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
mustNoErr := func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collectInis := func(ref string) map[string]setting.ConfigProvider {
|
||||||
|
inis := map[string]setting.ConfigProvider{}
|
||||||
|
err := filepath.WalkDir("options/locale", func(path string, d os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() || !strings.HasSuffix(d.Name(), ".ini") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cfg, err := setting.NewConfigProviderForLocale(path)
|
||||||
|
mustNoErr(err)
|
||||||
|
inis[path] = cfg
|
||||||
|
fmt.Printf("collecting: %s @ %s\n", path, ref)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
mustNoErr(err)
|
||||||
|
return inis
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect new locales from current working directory
|
||||||
|
inisNew := collectInis("HEAD")
|
||||||
|
|
||||||
|
// switch to the target ref, and collect the old locales
|
||||||
|
cmd := exec.Command("git", "checkout", os.Args[1])
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
mustNoErr(cmd.Run())
|
||||||
|
inisOld := collectInis(os.Args[1])
|
||||||
|
|
||||||
|
// use old en-US as the base, and copy the new translations to the old locales
|
||||||
|
enUsOld := inisOld["options/locale/locale_en-US.ini"]
|
||||||
|
brokenWarned := make(container.Set[string])
|
||||||
|
for path, iniOld := range inisOld {
|
||||||
|
if iniOld == enUsOld {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
iniNew := inisNew[path]
|
||||||
|
if iniNew == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, secEnUS := range enUsOld.Sections() {
|
||||||
|
secOld := iniOld.Section(secEnUS.Name())
|
||||||
|
secNew := iniNew.Section(secEnUS.Name())
|
||||||
|
for _, keyEnUs := range secEnUS.Keys() {
|
||||||
|
if secNew.HasKey(keyEnUs.Name()) {
|
||||||
|
oldStr := secOld.Key(keyEnUs.Name()).String()
|
||||||
|
newStr := secNew.Key(keyEnUs.Name()).String()
|
||||||
|
broken := oldStr != "" && strings.Count(oldStr, "%") != strings.Count(newStr, "%")
|
||||||
|
broken = broken || strings.Contains(oldStr, "\n") || strings.Contains(oldStr, "\n")
|
||||||
|
if broken {
|
||||||
|
brokenWarned.Add(secOld.Name() + "." + keyEnUs.Name())
|
||||||
|
fmt.Println("----")
|
||||||
|
fmt.Printf("WARNING: skip broken locale: %s , [%s] %s\n", path, secEnUS.Name(), keyEnUs.Name())
|
||||||
|
fmt.Printf("\told: %s\n", strings.ReplaceAll(oldStr, "\n", "\\n"))
|
||||||
|
fmt.Printf("\tnew: %s\n", strings.ReplaceAll(newStr, "\n", "\\n"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
secOld.Key(keyEnUs.Name()).SetValue(newStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mustNoErr(iniOld.SaveTo(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("========")
|
||||||
|
|
||||||
|
for path, iniNew := range inisNew {
|
||||||
|
for _, sec := range iniNew.Sections() {
|
||||||
|
for _, key := range sec.Keys() {
|
||||||
|
str := sec.Key(key.Name()).String()
|
||||||
|
broken := strings.Contains(str, "\n")
|
||||||
|
broken = broken || strings.HasPrefix(str, "`") != strings.HasSuffix(str, "`")
|
||||||
|
broken = broken || strings.HasPrefix(str, "\"`")
|
||||||
|
broken = broken || strings.HasPrefix(str, "`\"")
|
||||||
|
broken = broken || strings.Count(str, `"`)%2 == 1
|
||||||
|
broken = broken || strings.Count(str, "`")%2 == 1
|
||||||
|
if broken && !brokenWarned.Contains(sec.Name()+"."+key.Name()) {
|
||||||
|
fmt.Printf("WARNING: found broken locale: %s , [%s] %s\n", path, sec.Name(), key.Name())
|
||||||
|
fmt.Printf("\tstr: %s\n", strings.ReplaceAll(str, "\n", "\\n"))
|
||||||
|
fmt.Println("----")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
281
build/code-batch-process.go
Normal file
281
build/code-batch-process.go
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/build/codeformat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows has a limitation for command line arguments, the size can not exceed 32KB.
|
||||||
|
// So we have to feed the files to some tools (like gofmt) batch by batch
|
||||||
|
|
||||||
|
// We also introduce a `gitea-fmt` command, it does better import formatting than gofmt/goimports. `gitea-fmt` calls `gofmt` internally.
|
||||||
|
|
||||||
|
var optionLogVerbose bool
|
||||||
|
|
||||||
|
func logVerbose(msg string, args ...any) {
|
||||||
|
if optionLogVerbose {
|
||||||
|
log.Printf(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func passThroughCmd(cmd string, args []string) error {
|
||||||
|
foundCmd, err := exec.LookPath(cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can not find cmd: %s", cmd)
|
||||||
|
}
|
||||||
|
c := exec.Cmd{
|
||||||
|
Path: foundCmd,
|
||||||
|
Args: append([]string{cmd}, args...),
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
return c.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileCollector struct {
|
||||||
|
dirs []string
|
||||||
|
includePatterns []*regexp.Regexp
|
||||||
|
excludePatterns []*regexp.Regexp
|
||||||
|
batchSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error) {
|
||||||
|
co := &fileCollector{batchSize: batchSize}
|
||||||
|
if fileFilter == "go-own" {
|
||||||
|
co.dirs = []string{
|
||||||
|
"build",
|
||||||
|
"cmd",
|
||||||
|
"contrib",
|
||||||
|
"tests",
|
||||||
|
"models",
|
||||||
|
"modules",
|
||||||
|
"routers",
|
||||||
|
"services",
|
||||||
|
}
|
||||||
|
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
|
||||||
|
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`\.pb\.go$`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
|
||||||
|
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
|
||||||
|
}
|
||||||
|
|
||||||
|
if co.dirs == nil {
|
||||||
|
return nil, fmt.Errorf("unknown file-filter: %s", fileFilter)
|
||||||
|
}
|
||||||
|
return co, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc *fileCollector) matchPatterns(path string, regexps []*regexp.Regexp) bool {
|
||||||
|
path = strings.ReplaceAll(path, "\\", "/")
|
||||||
|
for _, re := range regexps {
|
||||||
|
if re.MatchString(path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc *fileCollector) collectFiles() (res [][]string, err error) {
|
||||||
|
var batch []string
|
||||||
|
for _, dir := range fc.dirs {
|
||||||
|
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
||||||
|
include := len(fc.includePatterns) == 0 || fc.matchPatterns(path, fc.includePatterns)
|
||||||
|
exclude := fc.matchPatterns(path, fc.excludePatterns)
|
||||||
|
process := include && !exclude
|
||||||
|
if !process {
|
||||||
|
if d.IsDir() {
|
||||||
|
if exclude {
|
||||||
|
logVerbose("exclude dir %s", path)
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
// for a directory, if it is not excluded explicitly, we should walk into
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// for a file, we skip it if it shouldn't be processed
|
||||||
|
logVerbose("skip process %s", path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
// skip dir, we don't add dirs to the file list now
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(batch) >= fc.batchSize {
|
||||||
|
res = append(res, batch)
|
||||||
|
batch = nil
|
||||||
|
}
|
||||||
|
batch = append(batch, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = append(res, batch)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// substArgFiles expands the {file-list} to a real file list for commands
|
||||||
|
func substArgFiles(args, files []string) []string {
|
||||||
|
for i, s := range args {
|
||||||
|
if s == "{file-list}" {
|
||||||
|
newArgs := append(args[:i], files...)
|
||||||
|
newArgs = append(newArgs, args[i+1:]...)
|
||||||
|
return newArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func exitWithCmdErrors(subCmd string, subArgs []string, cmdErrors []error) {
|
||||||
|
for _, err := range cmdErrors {
|
||||||
|
if err != nil {
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
exitCode := exitError.ExitCode()
|
||||||
|
log.Printf("run command failed (code=%d): %s %v", exitCode, subCmd, subArgs)
|
||||||
|
os.Exit(exitCode)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("run command failed (err=%s) %s %v", err, subCmd, subArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string) {
|
||||||
|
mainOptions = map[string]string{}
|
||||||
|
for i := 1; i < len(os.Args); i++ {
|
||||||
|
arg := os.Args[i]
|
||||||
|
if arg == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if arg[0] == '-' {
|
||||||
|
arg = strings.TrimPrefix(arg, "-")
|
||||||
|
arg = strings.TrimPrefix(arg, "-")
|
||||||
|
fields := strings.SplitN(arg, "=", 2)
|
||||||
|
if len(fields) == 1 {
|
||||||
|
mainOptions[fields[0]] = "1"
|
||||||
|
} else {
|
||||||
|
mainOptions[fields[0]] = fields[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subCmd = arg
|
||||||
|
subArgs = os.Args[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func showUsage() {
|
||||||
|
fmt.Printf(`Usage: %[1]s [options] {command} [arguments]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--verbose
|
||||||
|
--file-filter=go-own
|
||||||
|
--batch-size=100
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
%[1]s gofmt ...
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
{file-list} the file list
|
||||||
|
|
||||||
|
Example:
|
||||||
|
%[1]s gofmt -s -d {file-list}
|
||||||
|
|
||||||
|
`, "file-batch-exec")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
|
||||||
|
fileFilter := mainOptions["file-filter"]
|
||||||
|
if fileFilter == "" {
|
||||||
|
fileFilter = "go-own"
|
||||||
|
}
|
||||||
|
batchSize, _ := strconv.Atoi(mainOptions["batch-size"])
|
||||||
|
if batchSize == 0 {
|
||||||
|
batchSize = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFileCollector(fileFilter, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsString(a []string, s string) bool {
|
||||||
|
for _, v := range a {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func giteaFormatGoImports(files []string, doWriteFile bool) error {
|
||||||
|
for _, file := range files {
|
||||||
|
if err := codeformat.FormatGoImports(file, doWriteFile); err != nil {
|
||||||
|
log.Printf("failed to format go imports: %s, err=%v", file, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mainOptions, subCmd, subArgs := parseArgs()
|
||||||
|
if subCmd == "" {
|
||||||
|
showUsage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
optionLogVerbose = mainOptions["verbose"] != ""
|
||||||
|
|
||||||
|
fc, err := newFileCollectorFromMainOptions(mainOptions)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can not create file collector: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fileBatches, err := fc.collectFiles()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can not collect files: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
processed := 0
|
||||||
|
var cmdErrors []error
|
||||||
|
for _, files := range fileBatches {
|
||||||
|
if len(files) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
substArgs := substArgFiles(subArgs, files)
|
||||||
|
logVerbose("batch cmd: %s %v", subCmd, substArgs)
|
||||||
|
switch subCmd {
|
||||||
|
case "gitea-fmt":
|
||||||
|
if containsString(subArgs, "-d") {
|
||||||
|
log.Print("the -d option is not supported by gitea-fmt")
|
||||||
|
}
|
||||||
|
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
|
||||||
|
cmdErrors = append(cmdErrors, passThroughCmd("gofmt", append([]string{"-w", "-r", "interface{} -> any"}, substArgs...)))
|
||||||
|
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra"}, substArgs...)))
|
||||||
|
default:
|
||||||
|
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
|
||||||
|
}
|
||||||
|
processed += len(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
logVerbose("processed %d files", processed)
|
||||||
|
exitWithCmdErrors(subCmd, subArgs, cmdErrors)
|
||||||
|
}
|
||||||
195
build/codeformat/formatimports.go
Normal file
195
build/codeformat/formatimports.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package codeformat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var importPackageGroupOrders = map[string]int{
|
||||||
|
"": 1, // internal
|
||||||
|
"code.gitea.io/gitea/": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidCommentBetweenImports = errors.New("comments between imported packages are invalid, please move comments to the end of the package line")
|
||||||
|
|
||||||
|
var (
|
||||||
|
importBlockBegin = []byte("\nimport (\n")
|
||||||
|
importBlockEnd = []byte("\n)")
|
||||||
|
)
|
||||||
|
|
||||||
|
type importLineParsed struct {
|
||||||
|
group string
|
||||||
|
pkg string
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseImportLine(line string) (*importLineParsed, error) {
|
||||||
|
il := &importLineParsed{content: line}
|
||||||
|
p1 := strings.IndexRune(line, '"')
|
||||||
|
if p1 == -1 {
|
||||||
|
return nil, errors.New("invalid import line: " + line)
|
||||||
|
}
|
||||||
|
p1++
|
||||||
|
p := strings.IndexRune(line[p1:], '"')
|
||||||
|
if p == -1 {
|
||||||
|
return nil, errors.New("invalid import line: " + line)
|
||||||
|
}
|
||||||
|
p2 := p1 + p
|
||||||
|
il.pkg = line[p1:p2]
|
||||||
|
|
||||||
|
pDot := strings.IndexRune(il.pkg, '.')
|
||||||
|
pSlash := strings.IndexRune(il.pkg, '/')
|
||||||
|
if pDot != -1 && pDot < pSlash {
|
||||||
|
il.group = "domain-package"
|
||||||
|
}
|
||||||
|
for groupName := range importPackageGroupOrders {
|
||||||
|
if groupName == "" {
|
||||||
|
continue // skip internal
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(il.pkg, groupName) {
|
||||||
|
il.group = groupName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return il, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
importLineGroup []*importLineParsed
|
||||||
|
importLineGroupMap map[string]importLineGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatGoImports(contentBytes []byte) ([]byte, error) {
|
||||||
|
p1 := bytes.Index(contentBytes, importBlockBegin)
|
||||||
|
if p1 == -1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
p1 += len(importBlockBegin)
|
||||||
|
p := bytes.Index(contentBytes[p1:], importBlockEnd)
|
||||||
|
if p == -1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
p2 := p1 + p
|
||||||
|
|
||||||
|
importGroups := importLineGroupMap{}
|
||||||
|
r := bytes.NewBuffer(contentBytes[p1:p2])
|
||||||
|
eof := false
|
||||||
|
for !eof {
|
||||||
|
line, err := r.ReadString('\n')
|
||||||
|
eof = err == io.EOF
|
||||||
|
if err != nil && !eof {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
if strings.HasPrefix(line, "//") || strings.HasPrefix(line, "/*") {
|
||||||
|
return nil, errInvalidCommentBetweenImports
|
||||||
|
}
|
||||||
|
importLine, err := parseImportLine(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
importGroups[importLine.group] = append(importGroups[importLine.group], importLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupNames []string
|
||||||
|
for groupName, importLines := range importGroups {
|
||||||
|
groupNames = append(groupNames, groupName)
|
||||||
|
sort.Slice(importLines, func(i, j int) bool {
|
||||||
|
return strings.Compare(importLines[i].pkg, importLines[j].pkg) < 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(groupNames, func(i, j int) bool {
|
||||||
|
n1 := groupNames[i]
|
||||||
|
n2 := groupNames[j]
|
||||||
|
o1 := importPackageGroupOrders[n1]
|
||||||
|
o2 := importPackageGroupOrders[n2]
|
||||||
|
if o1 != 0 && o2 != 0 {
|
||||||
|
return o1 < o2
|
||||||
|
}
|
||||||
|
if o1 == 0 && o2 == 0 {
|
||||||
|
return strings.Compare(n1, n2) < 0
|
||||||
|
}
|
||||||
|
return o1 != 0
|
||||||
|
})
|
||||||
|
|
||||||
|
formattedBlock := bytes.Buffer{}
|
||||||
|
for _, groupName := range groupNames {
|
||||||
|
hasNormalImports := false
|
||||||
|
hasDummyImports := false
|
||||||
|
// non-dummy import comes first
|
||||||
|
for _, importLine := range importGroups[groupName] {
|
||||||
|
if strings.HasPrefix(importLine.content, "_") {
|
||||||
|
hasDummyImports = true
|
||||||
|
} else {
|
||||||
|
formattedBlock.WriteString("\t" + importLine.content + "\n")
|
||||||
|
hasNormalImports = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dummy (_ "pkg") comes later
|
||||||
|
if hasDummyImports {
|
||||||
|
if hasNormalImports {
|
||||||
|
formattedBlock.WriteString("\n")
|
||||||
|
}
|
||||||
|
for _, importLine := range importGroups[groupName] {
|
||||||
|
if strings.HasPrefix(importLine.content, "_") {
|
||||||
|
formattedBlock.WriteString("\t" + importLine.content + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formattedBlock.WriteString("\n")
|
||||||
|
}
|
||||||
|
formattedBlockBytes := bytes.TrimRight(formattedBlock.Bytes(), "\n")
|
||||||
|
|
||||||
|
var formattedBytes []byte
|
||||||
|
formattedBytes = append(formattedBytes, contentBytes[:p1]...)
|
||||||
|
formattedBytes = append(formattedBytes, formattedBlockBytes...)
|
||||||
|
formattedBytes = append(formattedBytes, contentBytes[p2:]...)
|
||||||
|
return formattedBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatGoImports format the imports by our rules (see unit tests)
|
||||||
|
func FormatGoImports(file string, doWriteFile bool) error {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var contentBytes []byte
|
||||||
|
{
|
||||||
|
defer f.Close()
|
||||||
|
contentBytes, err = io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formattedBytes, err := formatGoImports(contentBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if formattedBytes == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if bytes.Equal(contentBytes, formattedBytes) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if doWriteFile {
|
||||||
|
f, err = os.OpenFile(file, os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = f.Write(formattedBytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
124
build/codeformat/formatimports_test.go
Normal file
124
build/codeformat/formatimports_test.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package codeformat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatImportsSimple(t *testing.T) {
|
||||||
|
formatted, err := formatGoImports([]byte(`
|
||||||
|
package codeformat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
`))
|
||||||
|
|
||||||
|
expected := `
|
||||||
|
package codeformat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, string(formatted))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatImportsGroup(t *testing.T) {
|
||||||
|
// gofmt/goimports won't group the packages, for example, they produce such code:
|
||||||
|
// "bytes"
|
||||||
|
// "image"
|
||||||
|
// (a blank line)
|
||||||
|
// "fmt"
|
||||||
|
// "image/color/palette"
|
||||||
|
// our formatter does better, and these packages are grouped into one.
|
||||||
|
|
||||||
|
formatted, err := formatGoImports([]byte(`
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
_ "image/gif" // for processing gif images
|
||||||
|
_ "image/jpeg" // for processing jpeg images
|
||||||
|
_ "image/png" // for processing png images
|
||||||
|
|
||||||
|
"code.gitea.io/other/package"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"xorm.io/the/package"
|
||||||
|
|
||||||
|
"github.com/issue9/identicon"
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
"github.com/oliamb/cutter"
|
||||||
|
)
|
||||||
|
`))
|
||||||
|
|
||||||
|
expected := `
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
_ "image/gif" // for processing gif images
|
||||||
|
_ "image/jpeg" // for processing jpeg images
|
||||||
|
_ "image/png" // for processing png images
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"code.gitea.io/other/package"
|
||||||
|
"github.com/issue9/identicon"
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
"github.com/oliamb/cutter"
|
||||||
|
"xorm.io/the/package"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, string(formatted))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatImportsInvalidComment(t *testing.T) {
|
||||||
|
// why we shouldn't write comments between imports: it breaks the grouping of imports
|
||||||
|
// for example:
|
||||||
|
// "pkg1"
|
||||||
|
// "pkg2"
|
||||||
|
// // a comment
|
||||||
|
// "pkgA"
|
||||||
|
// "pkgB"
|
||||||
|
// the comment splits the packages into two groups, pkg1/2 are sorted separately, pkgA/B are sorted separately
|
||||||
|
// we don't want such code, so the code should be:
|
||||||
|
// "pkg1"
|
||||||
|
// "pkg2"
|
||||||
|
// "pkgA" // a comment
|
||||||
|
// "pkgB"
|
||||||
|
|
||||||
|
_, err := formatGoImports([]byte(`
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/jpeg"
|
||||||
|
// for processing gif images
|
||||||
|
"image/gif"
|
||||||
|
)
|
||||||
|
`))
|
||||||
|
assert.ErrorIs(t, err, errInvalidCommentBetweenImports)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
gemojiURL = "https://raw.githubusercontent.com/rhysd/gemoji/537ff2d7e0496e9964824f7f73ec7ece88c9765a/db/emoji.json"
|
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
||||||
maxUnicodeVersion = 16
|
maxUnicodeVersion = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
|
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
|
||||||
@@ -149,8 +149,8 @@ func generate() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet)
|
// write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet)
|
||||||
file, _ := json.MarshalIndent(data, "", " ")
|
file, _ := json.Marshal(data)
|
||||||
_ = os.WriteFile("assets/emoji.json", append(file, '\n'), 0o644)
|
_ = os.WriteFile("assets/emoji.json", file, 0o644)
|
||||||
|
|
||||||
// Add skin tones to emoji that support it
|
// Add skin tones to emoji that support it
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -8,220 +8,99 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
// regexp is based on go-license, excluding README and NOTICE
|
// regexp is based on go-license, excluding README and NOTICE
|
||||||
// https://github.com/google/go-licenses/blob/master/licenses/find.go
|
// https://github.com/google/go-licenses/blob/master/licenses/find.go
|
||||||
// also defined in vite.config.ts
|
|
||||||
var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`)
|
var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`)
|
||||||
|
|
||||||
// primaryLicenseRe matches exact primary license filenames without suffixes.
|
|
||||||
// When a directory has both primary and variant files (e.g. LICENSE and
|
|
||||||
// LICENSE.docs), only the primary files are kept.
|
|
||||||
var primaryLicenseRe = regexp.MustCompile(`^(?i)(LICEN[SC]E|COPYING)$`)
|
|
||||||
|
|
||||||
// ignoredNames are LicenseEntry.Name values to exclude from the output.
|
|
||||||
var ignoredNames = map[string]bool{
|
|
||||||
"code.gitea.io/gitea": true,
|
|
||||||
"code.gitea.io/gitea/options/license": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var excludedExt = map[string]bool{
|
|
||||||
".gitignore": true,
|
|
||||||
".go": true,
|
|
||||||
".mod": true,
|
|
||||||
".sum": true,
|
|
||||||
".toml": true,
|
|
||||||
".yaml": true,
|
|
||||||
".yml": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModuleInfo struct {
|
|
||||||
Path string
|
|
||||||
Dir string
|
|
||||||
PkgDirs []string // directories of packages imported from this module
|
|
||||||
}
|
|
||||||
|
|
||||||
type LicenseEntry struct {
|
type LicenseEntry struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
LicenseText string `json:"licenseText"`
|
LicenseText string `json:"licenseText"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getModules returns all dependency modules with their local directory paths
|
func main() {
|
||||||
// and the package directories used from each module.
|
if len(os.Args) != 3 {
|
||||||
func getModules(goCmd string) []ModuleInfo {
|
fmt.Println("usage: go run generate-go-licenses.go <base-dir> <out-json-file>")
|
||||||
cmd := exec.Command(goCmd, "list", "-deps", "-f",
|
|
||||||
"{{if .Module}}{{.Module.Path}}\t{{.Module.Dir}}\t{{.Dir}}{{end}}", "./...")
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
// Use GOOS=linux with CGO to ensure we capture all platform-specific
|
|
||||||
// dependencies, matching the CI environment.
|
|
||||||
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=1")
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to run 'go list -deps': %v\n", err)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var modules []ModuleInfo
|
base, out := os.Args[1], os.Args[2]
|
||||||
seen := make(map[string]int) // module path -> index in modules
|
|
||||||
for _, line := range strings.Split(string(output), "\n") {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Split(line, "\t")
|
|
||||||
if len(parts) != 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
modPath, modDir, pkgDir := parts[0], parts[1], parts[2]
|
|
||||||
if idx, ok := seen[modPath]; ok {
|
|
||||||
modules[idx].PkgDirs = append(modules[idx].PkgDirs, pkgDir)
|
|
||||||
} else {
|
|
||||||
seen[modPath] = len(modules)
|
|
||||||
modules = append(modules, ModuleInfo{
|
|
||||||
Path: modPath,
|
|
||||||
Dir: modDir,
|
|
||||||
PkgDirs: []string{pkgDir},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modules
|
|
||||||
}
|
|
||||||
|
|
||||||
// findLicenseFiles scans a module's root directory and its used package
|
// Add ext for excluded files because license_test.go will be included for some reason.
|
||||||
// directories for license files. It also walks up from each package directory
|
// And there are more files that should be excluded, check with:
|
||||||
// to the module root, scanning intermediate directories. Subdirectory licenses
|
//
|
||||||
// are only included if their text differs from the root license(s).
|
// go run github.com/google/go-licenses@v1.6.0 save . --force --save_path=.go-licenses 2>/dev/null
|
||||||
func findLicenseFiles(mod ModuleInfo) []LicenseEntry {
|
// find .go-licenses -type f | while read FILE; do echo "${$(basename $FILE)##*.}"; done | sort -u
|
||||||
var entries []LicenseEntry
|
// AUTHORS
|
||||||
seenTexts := make(map[string]bool)
|
// COPYING
|
||||||
|
// LICENSE
|
||||||
|
// Makefile
|
||||||
|
// NOTICE
|
||||||
|
// gitignore
|
||||||
|
// go
|
||||||
|
// md
|
||||||
|
// mod
|
||||||
|
// sum
|
||||||
|
// toml
|
||||||
|
// txt
|
||||||
|
// yml
|
||||||
|
//
|
||||||
|
// It could be removed once we have a better regex.
|
||||||
|
excludedExt := container.SetOf(".gitignore", ".go", ".mod", ".sum", ".toml", ".yml")
|
||||||
|
|
||||||
// First, collect root-level license files.
|
var paths []string
|
||||||
entries = append(entries, scanDirForLicenses(mod.Dir, mod.Path, "")...)
|
err := filepath.WalkDir(base, func(path string, entry fs.DirEntry, err error) error {
|
||||||
for _, e := range entries {
|
|
||||||
seenTexts[e.LicenseText] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then check each package directory and all intermediate parent directories
|
|
||||||
// up to the module root for license files with unique text.
|
|
||||||
seenDirs := map[string]bool{mod.Dir: true}
|
|
||||||
for _, pkgDir := range mod.PkgDirs {
|
|
||||||
for dir := pkgDir; dir != mod.Dir && strings.HasPrefix(dir, mod.Dir); dir = filepath.Dir(dir) {
|
|
||||||
if seenDirs[dir] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seenDirs[dir] = true
|
|
||||||
for _, e := range scanDirForLicenses(dir, mod.Path, mod.Dir) {
|
|
||||||
if !seenTexts[e.LicenseText] {
|
|
||||||
seenTexts[e.LicenseText] = true
|
|
||||||
entries = append(entries, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanDirForLicenses reads a single directory for license files and returns entries.
|
|
||||||
// If moduleRoot is non-empty, paths are made relative to it.
|
|
||||||
func scanDirForLicenses(dir, modulePath, moduleRoot string) []LicenseEntry {
|
|
||||||
dirEntries, err := os.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries []LicenseEntry
|
|
||||||
for _, entry := range dirEntries {
|
|
||||||
if entry.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := entry.Name()
|
|
||||||
if !licenseRe.MatchString(name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if excludedExt[strings.ToLower(filepath.Ext(name))] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := os.ReadFile(filepath.Join(dir, name))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
return err
|
||||||
|
}
|
||||||
|
if entry.IsDir() || !licenseRe.MatchString(entry.Name()) || excludedExt.Contains(filepath.Ext(entry.Name())) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
paths = append(paths, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(paths)
|
||||||
|
|
||||||
|
var entries []LicenseEntry
|
||||||
|
for _, filePath := range paths {
|
||||||
|
licenseText, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entryName := modulePath
|
pkgPath := filepath.ToSlash(filePath)
|
||||||
entryPath := modulePath + "/" + name
|
pkgPath = strings.TrimPrefix(pkgPath, base+"/")
|
||||||
if moduleRoot != "" {
|
pkgName := path.Dir(pkgPath)
|
||||||
rel, _ := filepath.Rel(moduleRoot, dir)
|
|
||||||
if rel != "." {
|
// There might be a bug somewhere in go-licenses that sometimes interprets the
|
||||||
relSlash := filepath.ToSlash(rel)
|
// root package as "." and sometimes as "code.gitea.io/gitea". Workaround by
|
||||||
entryName = modulePath + "/" + relSlash
|
// removing both of them for the sake of stable output.
|
||||||
entryPath = modulePath + "/" + relSlash + "/" + name
|
if pkgName == "." || pkgName == "code.gitea.io/gitea" {
|
||||||
}
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = append(entries, LicenseEntry{
|
entries = append(entries, LicenseEntry{
|
||||||
Name: entryName,
|
Name: pkgName,
|
||||||
Path: entryPath,
|
Path: pkgPath,
|
||||||
LicenseText: string(content),
|
LicenseText: string(licenseText),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// When multiple license files exist, prefer primary files (e.g. LICENSE)
|
|
||||||
// over variants with suffixes (e.g. LICENSE.docs, LICENSE-2.0.txt).
|
|
||||||
// If no primary file exists, keep only the first variant.
|
|
||||||
if len(entries) > 1 {
|
|
||||||
var primary []LicenseEntry
|
|
||||||
for _, e := range entries {
|
|
||||||
fileName := e.Path[strings.LastIndex(e.Path, "/")+1:]
|
|
||||||
if primaryLicenseRe.MatchString(fileName) {
|
|
||||||
primary = append(primary, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(primary) > 0 {
|
|
||||||
return primary
|
|
||||||
}
|
|
||||||
return entries[:1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) != 2 {
|
|
||||||
fmt.Println("usage: go run generate-go-licenses.go <out-json-file>")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
out := os.Args[1]
|
|
||||||
|
|
||||||
goCmd := "go"
|
|
||||||
if env := os.Getenv("GO"); env != "" {
|
|
||||||
goCmd = env
|
|
||||||
}
|
|
||||||
|
|
||||||
modules := getModules(goCmd)
|
|
||||||
|
|
||||||
var entries []LicenseEntry
|
|
||||||
for _, mod := range modules {
|
|
||||||
entries = append(entries, findLicenseFiles(mod)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
entries = slices.DeleteFunc(entries, func(e LicenseEntry) bool {
|
|
||||||
return ignoredNames[e.Name]
|
|
||||||
})
|
|
||||||
|
|
||||||
sort.Slice(entries, func(i, j int) bool {
|
|
||||||
return entries[i].Path < entries[j].Path
|
|
||||||
})
|
|
||||||
|
|
||||||
jsonBytes, err := json.MarshalIndent(entries, "", " ")
|
jsonBytes, err := json.MarshalIndent(entries, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -1,22 +1,52 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# this script runs in alpine image which only has `sh` shell
|
# this script runs in alpine image which only has `sh` shell
|
||||||
if [ ! -f ./options/locale/locale_en-US.json ]; then
|
|
||||||
|
set +e
|
||||||
|
if sed --version 2>/dev/null | grep -q GNU; then
|
||||||
|
SED_INPLACE="sed -i"
|
||||||
|
else
|
||||||
|
SED_INPLACE="sed -i ''"
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ ! -f ./options/locale/locale_en-US.ini ]; then
|
||||||
echo "please run this script in the root directory of the project"
|
echo "please run this script in the root directory of the project"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv ./options/locale/locale_en-US.json ./options/
|
mv ./options/locale/locale_en-US.ini ./options/
|
||||||
|
|
||||||
|
# the "ini" library for locale has many quirks, its behavior is different from Crowdin.
|
||||||
|
# see i18n_test.go for more details
|
||||||
|
|
||||||
|
# this script helps to unquote the Crowdin outputs for the quirky ini library
|
||||||
|
# * find all `key="...\"..."` lines
|
||||||
|
# * remove the leading quote
|
||||||
|
# * remove the trailing quote
|
||||||
|
# * unescape the quotes
|
||||||
|
# * eg: key="...\"..." => key=..."...
|
||||||
|
$SED_INPLACE -r -e '/^[-.A-Za-z0-9_]+[ ]*=[ ]*".*"$/ {
|
||||||
|
s/^([-.A-Za-z0-9_]+)[ ]*=[ ]*"/\1=/
|
||||||
|
s/"$//
|
||||||
|
s/\\"/"/g
|
||||||
|
}' ./options/locale/*.ini
|
||||||
|
|
||||||
|
# * if the escaped line is incomplete like `key="...` or `key=..."`, quote it with backticks
|
||||||
|
# * eg: key="... => key=`"...`
|
||||||
|
# * eg: key=..." => key=`..."`
|
||||||
|
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*(".*[^"])$/\1=`\2`/' ./options/locale/*.ini
|
||||||
|
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*([^"].*")$/\1=`\2`/' ./options/locale/*.ini
|
||||||
|
|
||||||
# Remove translation under 25% of en_us
|
# Remove translation under 25% of en_us
|
||||||
baselines=$(cat "./options/locale_en-US.json" | wc -l)
|
baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1)
|
||||||
baselines=$((baselines / 4))
|
baselines=$((baselines / 4))
|
||||||
for filename in ./options/locale/*.json; do
|
for filename in ./options/locale/*.ini; do
|
||||||
lines=$(cat "$filename" | wc -l)
|
lines=$(wc -l "$filename" | cut -d" " -f1)
|
||||||
if [ "$lines" -lt "$baselines" ]; then
|
if [ $lines -lt $baselines ]; then
|
||||||
echo "Removing $filename: $lines/$baselines"
|
echo "Removing $filename: $lines/$baselines"
|
||||||
rm "$filename"
|
rm "$filename"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
mv ./options/locale_en-US.json ./options/locale/
|
mv ./options/locale_en-US.ini ./options/locale/
|
||||||
|
|||||||
@@ -4,27 +4,25 @@
|
|||||||
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 newActionsCommand() *cli.Command {
|
var (
|
||||||
return &cli.Command{
|
// CmdActions represents the available actions sub-commands.
|
||||||
|
CmdActions = &cli.Command{
|
||||||
Name: "actions",
|
Name: "actions",
|
||||||
Usage: "Manage Gitea Actions",
|
Usage: "Manage Gitea Actions",
|
||||||
Commands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
newActionsGenerateRunnerTokenCommand(),
|
subcmdActionsGenRunnerToken,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newActionsGenerateRunnerTokenCommand() *cli.Command {
|
subcmdActionsGenRunnerToken = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "generate-runner-token",
|
Name: "generate-runner-token",
|
||||||
Usage: "Generate a new token for a runner to use to register with the server",
|
Usage: "Generate a new token for a runner to use to register with the server",
|
||||||
Action: runGenerateActionsRunnerToken,
|
Action: runGenerateActionsRunnerToken,
|
||||||
@@ -38,9 +36,12 @@ func newActionsGenerateRunnerTokenCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
|
func runGenerateActionsRunnerToken(c *cli.Context) error {
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
||||||
scope := c.String("scope")
|
scope := c.String("scope")
|
||||||
|
|||||||
90
cmd/admin.go
90
cmd/admin.go
@@ -15,71 +15,64 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newAdminCommand() *cli.Command {
|
var (
|
||||||
return &cli.Command{
|
// CmdAdmin represents the available admin sub-command.
|
||||||
|
CmdAdmin = &cli.Command{
|
||||||
Name: "admin",
|
Name: "admin",
|
||||||
Usage: "Perform common administrative operations",
|
Usage: "Perform common administrative operations",
|
||||||
Commands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
newUserCommand(),
|
subcmdUser,
|
||||||
newRepoSyncReleasesCommand(),
|
subcmdRepoSyncReleases,
|
||||||
newRegenerateCommand(),
|
subcmdRegenerate,
|
||||||
newAuthCommand(),
|
subcmdAuth,
|
||||||
newSendMailCommand(),
|
subcmdSendMail,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newRepoSyncReleasesCommand() *cli.Command {
|
subcmdRepoSyncReleases = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "repo-sync-releases",
|
Name: "repo-sync-releases",
|
||||||
Usage: "Synchronize repository releases with tags",
|
Usage: "Synchronize repository releases with tags",
|
||||||
Action: runRepoSyncReleases,
|
Action: runRepoSyncReleases,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newRegenerateCommand() *cli.Command {
|
subcmdRegenerate = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "regenerate",
|
Name: "regenerate",
|
||||||
Usage: "Regenerate specific files",
|
Usage: "Regenerate specific files",
|
||||||
Commands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
newRegenerateHooksCommand(),
|
microcmdRegenHooks,
|
||||||
newRegenerateKeysCommand(),
|
microcmdRegenKeys,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newAuthCommand() *cli.Command {
|
subcmdAuth = &cli.Command{
|
||||||
return &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,
|
||||||
newAuthListCommand(),
|
microcmdAuthList,
|
||||||
newAuthDeleteCommand(),
|
microcmdAuthDelete,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newSendMailCommand() *cli.Command {
|
subcmdSendMail = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "sendmail",
|
Name: "sendmail",
|
||||||
Usage: "Send a message to all users",
|
Usage: "Send a message to all users",
|
||||||
Action: runSendMail,
|
Action: runSendMail,
|
||||||
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",
|
||||||
@@ -93,27 +86,28 @@ func newSendMailCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -128,7 +122,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
|||||||
}
|
}
|
||||||
log.Trace("Processing next %d repos of %d", len(repos), count)
|
log.Trace("Processing next %d repos of %d", len(repos), count)
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RelativePath())
|
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
|
||||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("OpenRepository: %v", err)
|
log.Warn("OpenRepository: %v", err)
|
||||||
@@ -141,7 +135,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
|||||||
}
|
}
|
||||||
log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
|
log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
|
||||||
|
|
||||||
if _, err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||||
log.Warn(" SyncReleasesWithTags: %v", err)
|
log.Warn(" SyncReleasesWithTags: %v", err)
|
||||||
gitRepo.Close()
|
gitRepo.Close()
|
||||||
continue
|
continue
|
||||||
@@ -154,7 +148,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("repo %s releases synchronized to tags: from %d to %d",
|
log.Trace(" repo %s releases synchronized to tags: from %d to %d",
|
||||||
repo.FullName(), oldnum, count)
|
repo.FullName(), oldnum, count)
|
||||||
gitRepo.Close()
|
gitRepo.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -14,20 +13,17 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newAuthDeleteCommand() *cli.Command {
|
var (
|
||||||
return &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{
|
||||||
|
|
||||||
func newAuthListCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "List auth sources",
|
Usage: "List auth sources",
|
||||||
Action: runListAuth,
|
Action: runListAuth,
|
||||||
@@ -58,9 +54,12 @@ func newAuthListCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
|
func runListAuth(c *cli.Context) error {
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
func runListAuth(ctx context.Context, c *cli.Command) error {
|
|
||||||
if err := initDB(ctx); err != nil {
|
if err := initDB(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -91,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.",
|
||||||
@@ -94,10 +94,6 @@ func commonLdapCLIFlags() []cli.Flag {
|
|||||||
Name: "public-ssh-key-attribute",
|
Name: "public-ssh-key-attribute",
|
||||||
Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.",
|
Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "ssh-keys-are-verified",
|
|
||||||
Usage: "Set to true to automatically flag SSH keys in LDAP as verified.",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "skip-local-2fa",
|
Name: "skip-local-2fa",
|
||||||
Usage: "Set to true to skip local 2fa for users authenticated by this source",
|
Usage: "Set to true to skip local 2fa for users authenticated by this source",
|
||||||
@@ -107,10 +103,8 @@ func commonLdapCLIFlags() []cli.Flag {
|
|||||||
Usage: "The attribute of the user’s LDAP record containing the user’s avatar.",
|
Usage: "The attribute of the user’s LDAP record containing the user’s 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.",
|
||||||
@@ -163,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 {
|
||||||
@@ -228,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")
|
||||||
}
|
}
|
||||||
@@ -248,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")
|
||||||
}
|
}
|
||||||
@@ -261,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
|
||||||
}
|
}
|
||||||
@@ -298,9 +282,6 @@ func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
|
|||||||
if c.IsSet("public-ssh-key-attribute") {
|
if c.IsSet("public-ssh-key-attribute") {
|
||||||
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
|
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
|
||||||
}
|
}
|
||||||
if c.IsSet("ssh-keys-are-verified") {
|
|
||||||
config.SSHKeysAreVerified = c.Bool("ssh-keys-are-verified")
|
|
||||||
}
|
|
||||||
if c.IsSet("avatar-attribute") {
|
if c.IsSet("avatar-attribute") {
|
||||||
config.AttributeAvatar = c.String("avatar-attribute")
|
config.AttributeAvatar = c.String("avatar-attribute")
|
||||||
}
|
}
|
||||||
@@ -356,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
|
||||||
}
|
}
|
||||||
@@ -398,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
|
||||||
}
|
}
|
||||||
@@ -417,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
|
||||||
}
|
}
|
||||||
@@ -443,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -233,18 +234,17 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||||
assert.FailNow(t, "getAuthSourceByID called", "case %d: should not call getAuthSourceByID", n)
|
assert.FailNow(t, "getAuthSourceByID called", "case %d: should not call getAuthSourceByID", n)
|
||||||
return nil, nil //nolint:nilnil // mock function covering improper behavior
|
return nil, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
{
|
{
|
||||||
@@ -463,18 +465,17 @@ func TestAddLdapSimpleAuth(t *testing.T) {
|
|||||||
},
|
},
|
||||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||||
assert.FailNow(t, "getAuthSourceById called", "case %d: should not call getAuthSourceByID", n)
|
assert.FailNow(t, "getAuthSourceById called", "case %d: should not call getAuthSourceByID", n)
|
||||||
return nil, nil //nolint:nilnil // mock function covering improper behavior
|
return nil, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|||||||
@@ -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: runAddOauth,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Flags: oauthCLIFlags,
|
||||||
return newAuthService().runAddOauth(ctx, cmd)
|
|
||||||
},
|
|
||||||
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: runUpdateOauth,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
|
||||||
return newAuthService().runUpdateOauth(ctx, cmd)
|
|
||||||
},
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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: "",
|
||||||
@@ -68,34 +67,23 @@ func smtpCLIFlags() []cli.Flag {
|
|||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func microcmdAuthUpdateSMTP() *cli.Command {
|
microcmdAuthAddSMTP = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "add-smtp",
|
||||||
Name: "update-smtp",
|
Usage: "Add new SMTP authentication source",
|
||||||
Usage: "Update existing SMTP authentication source",
|
Action: runAddSMTP,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Flags: smtpCLIFlags,
|
||||||
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 {
|
microcmdAuthUpdateSMTP = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "update-smtp",
|
||||||
Name: "add-smtp",
|
Usage: "Update existing SMTP authentication source",
|
||||||
Usage: "Add new SMTP authentication source",
|
Action: runUpdateSMTP,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
|
||||||
return newAuthService().runAddSMTP(ctx, cmd)
|
|
||||||
},
|
|
||||||
Flags: smtpCLIFlags(),
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
|
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 +116,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 +148,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 +157,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 +190,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)
|
||||||
}
|
}
|
||||||
@@ -4,39 +4,41 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRegenerateHooksCommand() *cli.Command {
|
var (
|
||||||
return &cli.Command{
|
microcmdRegenHooks = &cli.Command{
|
||||||
Name: "hooks",
|
Name: "hooks",
|
||||||
Usage: "Regenerate git-hooks",
|
Usage: "Regenerate git-hooks",
|
||||||
Action: runRegenerateHooks,
|
Action: runRegenerateHooks,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newRegenerateKeysCommand() *cli.Command {
|
microcmdRegenKeys = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "keys",
|
Name: "keys",
|
||||||
Usage: "Regenerate authorized_keys file",
|
Usage: "Regenerate authorized_keys file",
|
||||||
Action: runRegenerateKeys,
|
Action: runRegenerateKeys,
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
|
func runRegenerateHooks(_ *cli.Context) error {
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,18 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUserCommand() *cli.Command {
|
var subcmdUser = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "user",
|
||||||
Name: "user",
|
Usage: "Modify users",
|
||||||
Usage: "Modify users",
|
Subcommands: []*cli.Command{
|
||||||
Commands: []*cli.Command{
|
microcmdUserCreate,
|
||||||
microcmdUserCreate(),
|
microcmdUserList,
|
||||||
newUserListCommand(),
|
microcmdUserChangePassword,
|
||||||
microcmdUserChangePassword(),
|
microcmdUserDelete,
|
||||||
microcmdUserDelete(),
|
microcmdUserGenerateAccessToken,
|
||||||
newUserGenerateAccessTokenCommand(),
|
microcmdUserMustChangePassword,
|
||||||
microcmdUserMustChangePassword(),
|
},
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -14,41 +13,44 @@ 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,
|
Flags: []cli.Flag{
|
||||||
Flags: []cli.Flag{
|
&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{
|
|
||||||
Name: "password",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "New password to set for user",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "must-change-password",
|
|
||||||
Usage: "User must change password (can be disabled by --must-change-password=false)",
|
|
||||||
Value: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "New password to set for user",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "must-change-password",
|
||||||
|
Usage: "User must change password (can be disabled by --must-change-password=false)",
|
||||||
|
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"))
|
||||||
|
|||||||
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -16,95 +16,87 @@ 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,
|
Flags: []cli.Flag{
|
||||||
MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{
|
&cli.StringFlag{
|
||||||
{
|
Name: "name",
|
||||||
Flags: [][]cli.Flag{
|
Usage: "Username. DEPRECATED: use username instead",
|
||||||
{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Usage: "Username. DEPRECATED: use username instead",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "username",
|
|
||||||
Usage: "Username",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
&cli.StringFlag{
|
||||||
&cli.StringFlag{
|
Name: "username",
|
||||||
Name: "user-type",
|
Usage: "Username",
|
||||||
Usage: "Set user's type: individual or bot",
|
|
||||||
Value: "individual",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "password",
|
|
||||||
Usage: "User password",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "email",
|
|
||||||
Usage: "User email address",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "admin",
|
|
||||||
Usage: "User is an admin",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "random-password",
|
|
||||||
Usage: "Generate a random password for the user",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
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)",
|
|
||||||
HideDefault: true,
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "random-password-length",
|
|
||||||
Usage: "Length of the random password to be generated",
|
|
||||||
Value: 12,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "access-token",
|
|
||||||
Usage: "Generate access token for the user",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "access-token-name",
|
|
||||||
Usage: `Name of the generated access token`,
|
|
||||||
Value: "gitea-admin",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "access-token-scopes",
|
|
||||||
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
|
||||||
Value: "all",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "restricted",
|
|
||||||
Usage: "Make a restricted user account",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "fullname",
|
|
||||||
Usage: `The full, human-readable name of the user`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "user-type",
|
||||||
|
Usage: "Set user's type: individual or bot",
|
||||||
|
Value: "individual",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Usage: "User password",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "email",
|
||||||
|
Usage: "User email address",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "admin",
|
||||||
|
Usage: "User is an admin",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "random-password",
|
||||||
|
Usage: "Generate a random password for the user",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
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)",
|
||||||
|
DisableDefaultText: true,
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "random-password-length",
|
||||||
|
Usage: "Length of the random password to be generated",
|
||||||
|
Value: 12,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "access-token",
|
||||||
|
Usage: "Generate access token for the user",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-name",
|
||||||
|
Usage: `Name of the generated access token`,
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "access-token-scopes",
|
||||||
|
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
|
Value: "all",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "restricted",
|
||||||
|
Usage: "Make a restricted user account",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "fullname",
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@@ -151,7 +153,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// codeql[disable-next-line=go/clear-text-logging]
|
|
||||||
fmt.Printf("generated random password is '%s'\n", password)
|
fmt.Printf("generated random password is '%s'\n", password)
|
||||||
} else if userType == user_model.UserTypeIndividual {
|
} else if userType == user_model.UserTypeIndividual {
|
||||||
return errors.New("must set either password or random-password flag")
|
return errors.New("must set either password or random-password flag")
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -4,56 +4,53 @@
|
|||||||
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{
|
&cli.Int64Flag{
|
||||||
&cli.Int64Flag{
|
Name: "id",
|
||||||
Name: "id",
|
Usage: "ID of user of the user to delete",
|
||||||
Usage: "ID of user of the user to delete",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "username",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Usage: "Username of the user to delete",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "email",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "Email of the user to delete",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "purge",
|
|
||||||
Usage: "Purge user, all their repositories, organizations and comments",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Action: runDeleteUser,
|
&cli.StringFlag{
|
||||||
}
|
Name: "username",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Usage: "Username of the user to delete",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "email",
|
||||||
|
Aliases: []string{"e"},
|
||||||
|
Usage: "Email of the user to delete",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "purge",
|
||||||
|
Usage: "Purge user, all their repositories, organizations and comments",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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()
|
||||||
if err := initDB(ctx); err != nil {
|
defer cancel()
|
||||||
return err
|
|
||||||
}
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := storage.Init(); err != nil {
|
if err := storage.Init(); err != nil {
|
||||||
@@ -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"))
|
||||||
|
|||||||
@@ -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{}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,51 +4,51 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUserGenerateAccessTokenCommand() *cli.Command {
|
var microcmdUserGenerateAccessToken = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "generate-access-token",
|
||||||
Name: "generate-access-token",
|
Usage: "Generate an access token for a specific user",
|
||||||
Usage: "Generate an access token for a specific user",
|
Flags: []cli.Flag{
|
||||||
Flags: []cli.Flag{
|
&cli.StringFlag{
|
||||||
&cli.StringFlag{
|
Name: "username",
|
||||||
Name: "username",
|
Aliases: []string{"u"},
|
||||||
Aliases: []string{"u"},
|
Usage: "Username",
|
||||||
Usage: "Username",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "token-name",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Token name",
|
|
||||||
Value: "gitea-admin",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "raw",
|
|
||||||
Usage: "Display only the token value",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "scopes",
|
|
||||||
Value: "all",
|
|
||||||
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Action: runGenerateAccessToken,
|
&cli.StringFlag{
|
||||||
}
|
Name: "token-name",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Token name",
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "raw",
|
||||||
|
Usage: "Display only the token value",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "scopes",
|
||||||
|
Value: "all",
|
||||||
|
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,31 +4,31 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUserListCommand() *cli.Command {
|
var microcmdUserList = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "list",
|
||||||
Name: "list",
|
Usage: "List users",
|
||||||
Usage: "List users",
|
Action: runListUsers,
|
||||||
Action: runListUsers,
|
Flags: []cli.Flag{
|
||||||
Flags: []cli.Flag{
|
&cli.BoolFlag{
|
||||||
&cli.BoolFlag{
|
Name: "admin",
|
||||||
Name: "admin",
|
Usage: "List only admin users",
|
||||||
Usage: "List only admin users",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,41 +4,40 @@
|
|||||||
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,
|
Flags: []cli.Flag{
|
||||||
Flags: []cli.Flag{
|
&cli.BoolFlag{
|
||||||
&cli.BoolFlag{
|
Name: "all",
|
||||||
Name: "all",
|
Aliases: []string{"A"},
|
||||||
Aliases: []string{"A"},
|
Usage: "All users must change password, except those explicitly excluded with --exclude",
|
||||||
Usage: "All users must change password, except those explicitly excluded with --exclude",
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "exclude",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "Do not change the must-change-password flag for these users",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "unset",
|
|
||||||
Usage: "Instead of setting the must-change-password flag, unset it",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringSliceFlag{
|
||||||
|
Name: "exclude",
|
||||||
|
Aliases: []string{"e"},
|
||||||
|
Usage: "Do not change the must-change-password flag for these users",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "unset",
|
||||||
|
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,10 +46,8 @@ 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)
|
||||||
@@ -58,7 +55,6 @@ func runMustChangePassword(ctx context.Context, c *cli.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// codeql[disable-next-line=go/clear-text-logging]
|
|
||||||
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
|
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
128
cmd/cert.go
128
cmd/cert.go
@@ -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,59 +20,47 @@ 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.
|
|
||||||
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||||
Action: runCert,
|
Action: runCert,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "host",
|
Name: "host",
|
||||||
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
Value: "",
|
||||||
Required: true,
|
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "ecdsa-curve",
|
|
||||||
Value: "",
|
|
||||||
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "rsa-bits",
|
|
||||||
Value: 3072,
|
|
||||||
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "start-date",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
|
|
||||||
},
|
|
||||||
&cli.DurationFlag{
|
|
||||||
Name: "duration",
|
|
||||||
Value: 365 * 24 * time.Hour,
|
|
||||||
Usage: "Duration that certificate is valid for",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "ca",
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "ecdsa-curve",
|
||||||
|
Value: "",
|
||||||
|
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "rsa-bits",
|
||||||
|
Value: 3072,
|
||||||
|
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "start-date",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
|
||||||
|
},
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "duration",
|
||||||
|
Value: 365 * 24 * time.Hour,
|
||||||
|
Usage: "Duration that certificate is valid for",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "ca",
|
||||||
|
Usage: "whether this cert should be its own Certificate Authority",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
123
cmd/cert_test.go
123
cmd/cert_test.go
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
cmd/cmd.go
31
cmd/cmd.go
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
156
cmd/config.go
156
cmd/config.go
@@ -1,156 +0,0 @@
|
|||||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func cmdConfig() *cli.Command {
|
|
||||||
subcmdConfigEditIni := &cli.Command{
|
|
||||||
Name: "edit-ini",
|
|
||||||
Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.",
|
|
||||||
Description: `
|
|
||||||
Help users to edit the Gitea configuration INI file.
|
|
||||||
|
|
||||||
# Keep Specified Keys
|
|
||||||
|
|
||||||
If you need to re-create the configuration file with only a subset of keys,
|
|
||||||
you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag.
|
|
||||||
For example, if a helm chart needs to reset the settings and only keep SECRET_KEY,
|
|
||||||
it can use a template file (only keys take effect, values are ignored):
|
|
||||||
|
|
||||||
[security]
|
|
||||||
SECRET_KEY=
|
|
||||||
|
|
||||||
$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini
|
|
||||||
|
|
||||||
# Map Environment Variables to INI Configuration
|
|
||||||
|
|
||||||
Environment variables of the form "GITEA__section_name__KEY_NAME"
|
|
||||||
will be mapped to the ini section "[section_name]" and the key
|
|
||||||
"KEY_NAME" with the value as provided.
|
|
||||||
|
|
||||||
Environment variables of the form "GITEA__section_name__KEY_NAME__FILE"
|
|
||||||
will be mapped to the ini section "[section_name]" and the key
|
|
||||||
"KEY_NAME" with the value loaded from the specified file.
|
|
||||||
|
|
||||||
Environment variable keys can only contain characters "0-9A-Z_",
|
|
||||||
if a section or key name contains dot ".", it needs to be escaped as _0x2E_.
|
|
||||||
For example, to apply this config:
|
|
||||||
|
|
||||||
[git.config]
|
|
||||||
foo.bar=val
|
|
||||||
|
|
||||||
$ export GITEA__git_0x2E_config__foo_0x2E_bar=val
|
|
||||||
|
|
||||||
# Put All Together
|
|
||||||
|
|
||||||
$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini}
|
|
||||||
`,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
// "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper
|
|
||||||
// "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "in-place",
|
|
||||||
Usage: "Output to the same config file as input. This flag will be ignored if --out is set.",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "config-keep-keys",
|
|
||||||
Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "apply-env",
|
|
||||||
Usage: "Apply all GITEA__* variables from the environment to the config.",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "out",
|
|
||||||
Usage: "Destination config file to write to.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: runConfigEditIni,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "config",
|
|
||||||
Usage: "Manage Gitea configuration",
|
|
||||||
Commands: []*cli.Command{
|
|
||||||
subcmdConfigEditIni,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runConfigEditIni(_ context.Context, c *cli.Command) error {
|
|
||||||
// the config system may change the environment variables, so get a copy first, to be used later
|
|
||||||
env := append([]string{}, os.Environ()...)
|
|
||||||
|
|
||||||
// don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly
|
|
||||||
if !c.IsSet("config") {
|
|
||||||
return errors.New("flag is required but not set: --config")
|
|
||||||
}
|
|
||||||
configFileIn := c.String("config")
|
|
||||||
|
|
||||||
cfgIn, err := setting.NewConfigProviderFromFile(configFileIn)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load config file %q: %v", configFileIn, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file
|
|
||||||
inPlace := c.Bool("in-place")
|
|
||||||
configFileOut := c.String("out")
|
|
||||||
if configFileOut == "" {
|
|
||||||
if !inPlace {
|
|
||||||
return errors.New("either --in-place or --out must be specified")
|
|
||||||
}
|
|
||||||
configFileOut = configFileIn // in-place edit
|
|
||||||
}
|
|
||||||
|
|
||||||
needWriteOut := configFileOut != configFileIn
|
|
||||||
|
|
||||||
cfgOut := cfgIn
|
|
||||||
configKeepKeys := c.String("config-keep-keys")
|
|
||||||
if configKeepKeys != "" {
|
|
||||||
needWriteOut = true
|
|
||||||
cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, secOut := range cfgOut.Sections() {
|
|
||||||
for _, keyOut := range secOut.Keys() {
|
|
||||||
secIn := cfgIn.Section(secOut.Name())
|
|
||||||
keyIn := setting.ConfigSectionKey(secIn, keyOut.Name())
|
|
||||||
if keyIn != nil {
|
|
||||||
keyOut.SetValue(keyIn.String())
|
|
||||||
} else {
|
|
||||||
secOut.DeleteKey(keyOut.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(secOut.Keys()) == 0 {
|
|
||||||
cfgOut.DeleteSection(secOut.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bool("apply-env") {
|
|
||||||
if setting.EnvironmentToConfig(cfgOut, env) {
|
|
||||||
needWriteOut = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needWriteOut {
|
|
||||||
err = cfgOut.SaveTo(configFileOut)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigEdit(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
configOld := tmpDir + "/app-old.ini"
|
|
||||||
configTemplate := tmpDir + "/app-template.ini"
|
|
||||||
_ = os.WriteFile(configOld, []byte(`
|
|
||||||
[sec]
|
|
||||||
k1=v1
|
|
||||||
k2=v2
|
|
||||||
`), os.ModePerm)
|
|
||||||
|
|
||||||
_ = os.WriteFile(configTemplate, []byte(`
|
|
||||||
[sec]
|
|
||||||
k1=in-template
|
|
||||||
|
|
||||||
[sec2]
|
|
||||||
k3=v3
|
|
||||||
`), os.ModePerm)
|
|
||||||
|
|
||||||
t.Setenv("GITEA__EnV__KeY", "val")
|
|
||||||
|
|
||||||
t.Run("OutputToNewWithEnv", func(t *testing.T) {
|
|
||||||
configNew := tmpDir + "/app-new.ini"
|
|
||||||
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
|
|
||||||
"./gitea", "--config", configOld,
|
|
||||||
"config", "edit-ini",
|
|
||||||
"--apply-env",
|
|
||||||
"--config-keep-keys", configTemplate,
|
|
||||||
"--out", configNew,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// "k1" old value is kept because its key is in the template
|
|
||||||
// "k2" is removed because it isn't in the template
|
|
||||||
// "k3" isn't in new config because it isn't in the old config
|
|
||||||
// [env] is applied from environment variable
|
|
||||||
data, _ := os.ReadFile(configNew)
|
|
||||||
require.Equal(t, `[sec]
|
|
||||||
k1 = v1
|
|
||||||
|
|
||||||
[env]
|
|
||||||
KeY = val
|
|
||||||
`, string(data))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OutputToExisting(environment-to-ini)", func(t *testing.T) {
|
|
||||||
// the legacy "environment-to-ini" (now a wrapper script) behavior:
|
|
||||||
// if no "--out", then "--in-place" must be used to overwrite the existing "--config" file
|
|
||||||
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
|
|
||||||
"./gitea", "config", "edit-ini",
|
|
||||||
"--apply-env",
|
|
||||||
"--config", configOld,
|
|
||||||
})
|
|
||||||
require.ErrorContains(t, err, "either --in-place or --out must be specified")
|
|
||||||
|
|
||||||
// simulate the "environment-to-ini" behavior with "--in-place"
|
|
||||||
err = NewMainApp(AppVersion{}).Run(t.Context(), []string{
|
|
||||||
"./gitea", "config", "edit-ini",
|
|
||||||
"--in-place",
|
|
||||||
"--apply-env",
|
|
||||||
"--config", configOld,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
data, _ := os.ReadFile(configOld)
|
|
||||||
require.Equal(t, `[sec]
|
|
||||||
k1 = v1
|
|
||||||
k2 = v2
|
|
||||||
|
|
||||||
[env]
|
|
||||||
KeY = val
|
|
||||||
`, string(data))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
51
cmd/docs.go
51
cmd/docs.go
@@ -4,45 +4,42 @@
|
|||||||
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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDocsCommand() *cli.Command {
|
// CmdDocs represents the available docs sub-command.
|
||||||
return &cli.Command{
|
var CmdDocs = &cli.Command{
|
||||||
Name: "docs",
|
Name: "docs",
|
||||||
Usage: "Output CLI documentation",
|
Usage: "Output CLI documentation",
|
||||||
Description: "A command to output Gitea's CLI documentation, optionally to a file.",
|
Description: "A command to output Gitea's CLI documentation, optionally to a file.",
|
||||||
Action: runDocs,
|
Action: runDocs,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "man",
|
Name: "man",
|
||||||
Usage: "Output man pages instead",
|
Usage: "Output man pages instead",
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "output",
|
|
||||||
Aliases: []string{"o"},
|
|
||||||
Usage: "Path to output to instead of stdout (will overwrite if exists)",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
Usage: "Path to output to instead of stdout (will overwrite if exists)",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -54,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
|
||||||
}
|
}
|
||||||
|
|||||||
164
cmd/doctor.go
164
cmd/doctor.go
@@ -20,90 +20,89 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDoctorCommand() *cli.Command {
|
// CmdDoctor represents the available doctor sub-command.
|
||||||
return &cli.Command{
|
var CmdDoctor = &cli.Command{
|
||||||
Name: "doctor",
|
Name: "doctor",
|
||||||
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{
|
|
||||||
newDoctorCheckCommand(),
|
Subcommands: []*cli.Command{
|
||||||
newRecreateTableCommand(),
|
cmdDoctorCheck,
|
||||||
newDoctorConvertCommand(),
|
cmdRecreateTable,
|
||||||
},
|
cmdDoctorConvert,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDoctorCheckCommand() *cli.Command {
|
var cmdDoctorCheck = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "check",
|
||||||
Name: "check",
|
Usage: "Diagnose and optionally fix problems",
|
||||||
Usage: "Diagnose and optionally fix problems",
|
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||||
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.",
|
Action: runDoctorCheck,
|
||||||
Action: runDoctorCheck,
|
Flags: []cli.Flag{
|
||||||
Flags: []cli.Flag{
|
&cli.BoolFlag{
|
||||||
&cli.BoolFlag{
|
Name: "list",
|
||||||
Name: "list",
|
Usage: "List the available checks",
|
||||||
Usage: "List the available checks",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "default",
|
|
||||||
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "run",
|
|
||||||
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "all",
|
|
||||||
Usage: "Run all the available checks",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "fix",
|
|
||||||
Usage: "Automatically fix what we can",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "log-file",
|
|
||||||
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "color",
|
|
||||||
Aliases: []string{"H"},
|
|
||||||
Usage: "Use color for outputted information",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.BoolFlag{
|
||||||
|
Name: "default",
|
||||||
|
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "all",
|
||||||
|
Usage: "Run all the available checks",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "fix",
|
||||||
|
Usage: "Automatically fix what we can",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "log-file",
|
||||||
|
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "color",
|
||||||
|
Aliases: []string{"H"},
|
||||||
|
Usage: "Use color for outputted information",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRecreateTableCommand() *cli.Command {
|
var cmdRecreateTable = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "recreate-table",
|
||||||
Name: "recreate-table",
|
Usage: "Recreate tables from XORM definitions and copy the data.",
|
||||||
Usage: "Recreate tables from XORM definitions and copy the data.",
|
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
|
||||||
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
|
Flags: []cli.Flag{
|
||||||
Flags: []cli.Flag{
|
&cli.BoolFlag{
|
||||||
&cli.BoolFlag{
|
Name: "debug",
|
||||||
Name: "debug",
|
Usage: "Print SQL commands sent",
|
||||||
Usage: "Print SQL commands sent",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns.
|
},
|
||||||
|
Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns.
|
||||||
|
|
||||||
This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
|
This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
|
||||||
|
|
||||||
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
|
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
|
||||||
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()
|
||||||
|
|
||||||
@@ -114,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,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
|
||||||
}
|
}
|
||||||
@@ -140,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
|
||||||
@@ -162,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)
|
||||||
@@ -193,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)
|
||||||
@@ -215,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,27 +4,28 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDoctorConvertCommand() *cli.Command {
|
// cmdDoctorConvert represents the available convert sub-command.
|
||||||
return &cli.Command{
|
var cmdDoctorConvert = &cli.Command{
|
||||||
Name: "convert",
|
Name: "convert",
|
||||||
Usage: "Convert the database",
|
Usage: "Convert the database",
|
||||||
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",
|
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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{newDoctorCheckCommand()},
|
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"`)
|
||||||
}
|
}
|
||||||
|
|||||||
219
cmd/dump.go
219
cmd/dump.go
@@ -5,7 +5,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -20,99 +19,99 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDumpCommand() *cli.Command {
|
// CmdDump represents the available dump sub-command.
|
||||||
return &cli.Command{
|
var CmdDump = &cli.Command{
|
||||||
Name: "dump",
|
Name: "dump",
|
||||||
Usage: "Dump Gitea files and database",
|
Usage: "Dump Gitea files and database",
|
||||||
Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`,
|
Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`,
|
||||||
Action: runDump,
|
Action: runDump,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "file",
|
Name: "file",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`,
|
Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`,
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "verbose",
|
|
||||||
Aliases: []string{"V"},
|
|
||||||
Usage: "Show process details",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "quiet",
|
|
||||||
Aliases: []string{"q"},
|
|
||||||
Usage: "Only display warnings and errors",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "tempdir",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Value: os.TempDir(),
|
|
||||||
Usage: "Temporary dir path",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "database",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-repository",
|
|
||||||
Aliases: []string{"R"},
|
|
||||||
Usage: "Skip the repository dumping",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-log",
|
|
||||||
Aliases: []string{"L"},
|
|
||||||
Usage: "Skip the log dumping",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-custom-dir",
|
|
||||||
Usage: "Skip custom directory",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-lfs-data",
|
|
||||||
Usage: "Skip LFS data",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-attachment-data",
|
|
||||||
Usage: "Skip attachment data",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-package-data",
|
|
||||||
Usage: "Skip package data",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-index",
|
|
||||||
Usage: "Skip bleve index data",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-db",
|
|
||||||
Usage: "Skip database",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "type",
|
|
||||||
Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Aliases: []string{"V"},
|
||||||
|
Usage: "Show process details",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "quiet",
|
||||||
|
Aliases: []string{"q"},
|
||||||
|
Usage: "Only display warnings and errors",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tempdir",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: os.TempDir(),
|
||||||
|
Usage: "Temporary dir path",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "database",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-repository",
|
||||||
|
Aliases: []string{"R"},
|
||||||
|
Usage: "Skip the repository dumping",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-log",
|
||||||
|
Aliases: []string{"L"},
|
||||||
|
Usage: "Skip the log dumping",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-custom-dir",
|
||||||
|
Usage: "Skip custom directory",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-lfs-data",
|
||||||
|
Usage: "Skip LFS data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-attachment-data",
|
||||||
|
Usage: "Skip attachment data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-package-data",
|
||||||
|
Usage: "Skip package data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-index",
|
||||||
|
Usage: "Skip bleve index data",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "skip-db",
|
||||||
|
Usage: "Skip database",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "type",
|
||||||
|
Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func fatal(format string, args ...any) {
|
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")
|
||||||
}
|
}
|
||||||
@@ -137,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
|
||||||
}
|
}
|
||||||
@@ -146,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)
|
||||||
@@ -167,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")
|
||||||
@@ -176,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)
|
||||||
}
|
}
|
||||||
@@ -203,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 {
|
||||||
@@ -214,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)
|
||||||
@@ -257,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)
|
||||||
}
|
}
|
||||||
@@ -266,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")
|
||||||
@@ -294,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)
|
||||||
}
|
}
|
||||||
@@ -302,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)
|
||||||
@@ -319,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)
|
||||||
}
|
}
|
||||||
|
|||||||
144
cmd/dump_repo.go
144
cmd/dump_repo.go
@@ -19,79 +19,81 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDumpRepositoryCommand() *cli.Command {
|
// CmdDumpRepository represents the available dump repository sub-command.
|
||||||
return &cli.Command{
|
var CmdDumpRepository = &cli.Command{
|
||||||
Name: "dump-repo",
|
Name: "dump-repo",
|
||||||
Usage: "Dump the repository from git/github/gitea/gitlab",
|
Usage: "Dump the repository from git/github/gitea/gitlab",
|
||||||
Description: "This is a command for dumping the repository data.",
|
Description: "This is a command for dumping the repository data.",
|
||||||
Action: runDumpRepository,
|
Action: runDumpRepository,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "git_service",
|
Name: "git_service",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
|
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "repo_dir",
|
|
||||||
Aliases: []string{"r"},
|
|
||||||
Value: "./data",
|
|
||||||
Usage: "Repository dir path to store the data",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "clone_addr",
|
|
||||||
Value: "",
|
|
||||||
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "auth_username",
|
|
||||||
Value: "",
|
|
||||||
Usage: "The username to visit the clone_addr",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "auth_password",
|
|
||||||
Value: "",
|
|
||||||
Usage: "The password to visit the clone_addr",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "auth_token",
|
|
||||||
Value: "",
|
|
||||||
Usage: "The personal token to visit the clone_addr",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "owner_name",
|
|
||||||
Value: "",
|
|
||||||
Usage: "The data will be stored on a directory with owner name if not empty",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "repo_name",
|
|
||||||
Value: "",
|
|
||||||
Usage: "The data will be stored on a directory with repository name if not empty",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "units",
|
|
||||||
Value: "",
|
|
||||||
Usage: `Which items will be migrated, one or more units should be separated as comma.
|
|
||||||
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "repo_dir",
|
||||||
|
Aliases: []string{"r"},
|
||||||
|
Value: "./data",
|
||||||
|
Usage: "Repository dir path to store the data",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "clone_addr",
|
||||||
|
Value: "",
|
||||||
|
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "auth_username",
|
||||||
|
Value: "",
|
||||||
|
Usage: "The username to visit the clone_addr",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "auth_password",
|
||||||
|
Value: "",
|
||||||
|
Usage: "The password to visit the clone_addr",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "auth_token",
|
||||||
|
Value: "",
|
||||||
|
Usage: "The personal token to visit the clone_addr",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "owner_name",
|
||||||
|
Value: "",
|
||||||
|
Usage: "The data will be stored on a directory with owner name if not empty",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "repo_name",
|
||||||
|
Value: "",
|
||||||
|
Usage: "The data will be stored on a directory with repository name if not empty",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "units",
|
||||||
|
Value: "",
|
||||||
|
Usage: `Which items will be migrated, one or more units should be separated as comma.
|
||||||
|
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
|
func runDumpRepository(ctx *cli.Context) error {
|
||||||
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
||||||
|
|
||||||
setting.DisableLoggerInit()
|
setting.DisableLoggerInit()
|
||||||
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
stdCtx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(stdCtx); 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +105,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/") {
|
||||||
@@ -122,13 +124,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
|
||||||
@@ -138,8 +140,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
|
||||||
@@ -167,7 +169,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 {
|
||||||
@@ -180,9 +182,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)
|
||||||
|
|||||||
@@ -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,26 +18,24 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var matchedAssetFiles []assetFile
|
// CmdEmbedded represents the available extract sub-command.
|
||||||
|
var (
|
||||||
func newEmbeddedCommand() *cli.Command {
|
CmdEmbedded = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
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{
|
||||||
newEmbeddedListCommand(),
|
subcmdList,
|
||||||
newEmbeddedViewCommand(),
|
subcmdView,
|
||||||
newEmbeddedExtractCommand(),
|
subcmdExtract,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newEmbeddedListCommand() *cli.Command {
|
subcmdList = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "List files matching the given pattern",
|
Usage: "List files matching the given pattern",
|
||||||
Action: runList,
|
Action: runList,
|
||||||
@@ -51,10 +47,8 @@ func newEmbeddedListCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newEmbeddedViewCommand() *cli.Command {
|
subcmdView = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "view",
|
Name: "view",
|
||||||
Usage: "View a file matching the given pattern",
|
Usage: "View a file matching the given pattern",
|
||||||
Action: runView,
|
Action: runView,
|
||||||
@@ -66,10 +60,8 @@ func newEmbeddedViewCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newEmbeddedExtractCommand() *cli.Command {
|
subcmdExtract = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "extract",
|
Name: "extract",
|
||||||
Usage: "Extract resources",
|
Usage: "Extract resources",
|
||||||
Action: runExtract,
|
Action: runExtract,
|
||||||
@@ -98,7 +90,9 @@ func newEmbeddedExtractCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
matchedAssetFiles []assetFile
|
||||||
|
)
|
||||||
|
|
||||||
type assetFile struct {
|
type assetFile struct {
|
||||||
fs *assetfs.LayeredFS
|
fs *assetfs.LayeredFS
|
||||||
@@ -106,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())
|
||||||
@@ -121,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
|
||||||
}
|
}
|
||||||
@@ -157,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
|
||||||
}
|
}
|
||||||
@@ -180,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
|
||||||
}
|
}
|
||||||
@@ -222,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,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 {
|
||||||
@@ -300,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
|
||||||
|
|||||||
@@ -5,64 +5,56 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newGenerateCommand() *cli.Command {
|
var (
|
||||||
return &cli.Command{
|
// CmdGenerate represents the available generate sub-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{
|
||||||
newGenerateSecretCommand(),
|
subcmdSecret,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newGenerateSecretCommand() *cli.Command {
|
subcmdSecret = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "secret",
|
Name: "secret",
|
||||||
Usage: "Generate a secret token",
|
Usage: "Generate a secret token",
|
||||||
Commands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
newGenerateInternalTokenCommand(),
|
microcmdGenerateInternalToken,
|
||||||
newGenerateLfsJWTSecretCommand(),
|
microcmdGenerateLfsJwtSecret,
|
||||||
newGenerateSecretKeyCommand(),
|
microcmdGenerateSecretKey,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newGenerateInternalTokenCommand() *cli.Command {
|
microcmdGenerateInternalToken = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "INTERNAL_TOKEN",
|
Name: "INTERNAL_TOKEN",
|
||||||
Usage: "Generate a new INTERNAL_TOKEN",
|
Usage: "Generate a new INTERNAL_TOKEN",
|
||||||
Action: runGenerateInternalToken,
|
Action: runGenerateInternalToken,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newGenerateLfsJWTSecretCommand() *cli.Command {
|
microcmdGenerateLfsJwtSecret = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "JWT_SECRET",
|
Name: "JWT_SECRET",
|
||||||
Aliases: []string{"LFS_JWT_SECRET"},
|
Aliases: []string{"LFS_JWT_SECRET"},
|
||||||
Usage: "Generate a new JWT_SECRET",
|
Usage: "Generate a new JWT_SECRET",
|
||||||
Action: runGenerateLfsJwtSecret,
|
Action: runGenerateLfsJwtSecret,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newGenerateSecretKeyCommand() *cli.Command {
|
microcmdGenerateSecretKey = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "SECRET_KEY",
|
Name: "SECRET_KEY",
|
||||||
Usage: "Generate a new SECRET_KEY",
|
Usage: "Generate a new SECRET_KEY",
|
||||||
Action: runGenerateSecretKey,
|
Action: runGenerateSecretKey,
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
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
|
||||||
@@ -77,8 +69,12 @@ 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 := generate.NewJwtSecretWithBase64()
|
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("%s", jwtSecretBase64)
|
fmt.Printf("%s", jwtSecretBase64)
|
||||||
|
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
@@ -88,13 +84,12 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// codeql[disable-next-line=go/clear-text-logging]
|
|
||||||
fmt.Printf("%s", secretKey)
|
fmt.Printf("%s", secretKey)
|
||||||
|
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
|
|||||||
116
cmd/hook.go
116
cmd/hook.go
@@ -15,37 +15,34 @@ 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 = 500
|
||||||
)
|
)
|
||||||
|
|
||||||
func newHookCommand() *cli.Command {
|
var (
|
||||||
return &cli.Command{
|
// CmdHook represents the available hooks sub-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{
|
||||||
newHookPreReceiveCommand(),
|
subcmdHookPreReceive,
|
||||||
newHookUpdateCommand(),
|
subcmdHookUpdate,
|
||||||
newHookPostReceiveCommand(),
|
subcmdHookPostReceive,
|
||||||
newHookProcReceiveCommand(),
|
subcmdHookProcReceive,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newHookPreReceiveCommand() *cli.Command {
|
subcmdHookPreReceive = &cli.Command{
|
||||||
return &cli.Command{
|
|
||||||
Name: "pre-receive",
|
Name: "pre-receive",
|
||||||
Usage: "Delegate pre-receive Git hook",
|
Usage: "Delegate pre-receive Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
@@ -56,10 +53,7 @@ func newHookPreReceiveCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
subcmdHookUpdate = &cli.Command{
|
||||||
|
|
||||||
func newHookUpdateCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "update",
|
Name: "update",
|
||||||
Usage: "Delegate update Git hook",
|
Usage: "Delegate update Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
@@ -70,10 +64,7 @@ func newHookUpdateCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
subcmdHookPostReceive = &cli.Command{
|
||||||
|
|
||||||
func newHookPostReceiveCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "post-receive",
|
Name: "post-receive",
|
||||||
Usage: "Delegate post-receive Git hook",
|
Usage: "Delegate post-receive Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
@@ -84,11 +75,8 @@ func newHookPostReceiveCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
// Note: new hook since git 2.29
|
||||||
|
subcmdHookProcReceive = &cli.Command{
|
||||||
// Note: new hook since git 2.29
|
|
||||||
func newHookProcReceiveCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "proc-receive",
|
Name: "proc-receive",
|
||||||
Usage: "Delegate proc-receive Git hook",
|
Usage: "Delegate proc-receive Git hook",
|
||||||
Description: "This command should only be called by Git",
|
Description: "This command should only be called by Git",
|
||||||
@@ -99,7 +87,7 @@ func newHookProcReceiveCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
type delayWriter struct {
|
type delayWriter struct {
|
||||||
internal io.Writer
|
internal io.Writer
|
||||||
@@ -173,18 +161,12 @@ func (n *nilWriter) WriteString(s string) (int, error) {
|
|||||||
return len(s), nil
|
return len(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, refFullName git.RefName, ok bool) {
|
func runHookPreReceive(c *cli.Context) error {
|
||||||
fields := strings.Split(line, " ")
|
|
||||||
if len(fields) != 3 {
|
|
||||||
return "", "", "", false
|
|
||||||
}
|
|
||||||
return fields[0], fields[1], git.RefName(fields[2]), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHookPreReceive(ctx context.Context, c *cli.Command) 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"))
|
||||||
|
|
||||||
@@ -204,7 +186,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||||
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
||||||
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
|
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
|
||||||
actionsTaskID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionsTaskID), 10, 64)
|
actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64)
|
||||||
|
|
||||||
hookOptions := private.HookOptions{
|
hookOptions := private.HookOptions{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@@ -214,8 +196,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
GitPushOptions: pushOptions(),
|
GitPushOptions: pushOptions(),
|
||||||
PullRequestID: prID,
|
PullRequestID: prID,
|
||||||
DeployKeyID: deployKeyID,
|
DeployKeyID: deployKeyID,
|
||||||
ActionsTaskID: actionsTaskID,
|
ActionPerm: int(actionPerm),
|
||||||
IsWiki: isWiki,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
@@ -247,11 +228,14 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
oldCommitID, newCommitID, refFullName, ok := parseGitHookCommitRefLine(scanner.Text())
|
fields := bytes.Fields(scanner.Bytes())
|
||||||
if !ok {
|
if len(fields) != 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldCommitID := string(fields[0])
|
||||||
|
newCommitID := string(fields[1])
|
||||||
|
refFullName := git.RefName(fields[2])
|
||||||
total++
|
total++
|
||||||
lastline++
|
lastline++
|
||||||
|
|
||||||
@@ -286,9 +270,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
lastline = 0
|
lastline = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
hookOptions.OldCommitIDs = oldCommitIDs[:count]
|
hookOptions.OldCommitIDs = oldCommitIDs[:count]
|
||||||
@@ -311,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
|
||||||
}
|
}
|
||||||
@@ -328,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").RunWithStderr(ctx); 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
|
||||||
@@ -380,7 +364,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
GitPushOptions: pushOptions(),
|
GitPushOptions: pushOptions(),
|
||||||
PullRequestID: prID,
|
PullRequestID: prID,
|
||||||
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
|
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
|
||||||
IsWiki: isWiki,
|
|
||||||
}
|
}
|
||||||
oldCommitIDs := make([]string, hookBatchSize)
|
oldCommitIDs := make([]string, hookBatchSize)
|
||||||
newCommitIDs := make([]string, hookBatchSize)
|
newCommitIDs := make([]string, hookBatchSize)
|
||||||
@@ -398,13 +381,16 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
fields := bytes.Fields(scanner.Bytes())
|
||||||
oldCommitIDs[count], newCommitIDs[count], refFullNames[count], ok = parseGitHookCommitRefLine(scanner.Text())
|
if len(fields) != 3 {
|
||||||
if !ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(out, ".")
|
fmt.Fprintf(out, ".")
|
||||||
|
oldCommitIDs[count] = string(fields[0])
|
||||||
|
newCommitIDs[count] = string(fields[1])
|
||||||
|
refFullNames[count] = git.RefName(fields[2])
|
||||||
|
|
||||||
commitID, _ := git.NewIDFromString(newCommitIDs[count])
|
commitID, _ := git.NewIDFromString(newCommitIDs[count])
|
||||||
if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
|
if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
|
||||||
masterPushed = true
|
masterPushed = true
|
||||||
@@ -428,11 +414,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
count = 0
|
count = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
_ = dWriter.Close()
|
|
||||||
hookPrintResults(results)
|
|
||||||
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
if wasEmpty && masterPushed {
|
if wasEmpty && masterPushed {
|
||||||
@@ -504,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 {
|
||||||
@@ -515,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 {
|
||||||
@@ -533,7 +517,6 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
||||||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
|
||||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||||
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||||
pusherName := os.Getenv(repo_module.EnvPusherName)
|
pusherName := os.Getenv(repo_module.EnvPusherName)
|
||||||
@@ -611,15 +594,14 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
UserName: pusherName,
|
UserName: pusherName,
|
||||||
UserID: pusherID,
|
UserID: pusherID,
|
||||||
GitPushOptions: make(map[string]string),
|
GitPushOptions: make(map[string]string),
|
||||||
IsWiki: isWiki,
|
|
||||||
}
|
}
|
||||||
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
|
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
|
||||||
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
|
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
|
||||||
hookOptions.RefFullNames = make([]git.RefName, 0, hookBatchSize)
|
hookOptions.RefFullNames = make([]git.RefName, 0, hookBatchSize)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// note: pktLineTypeUnknown means pktLineTypeFlush and pktLineTypeData all allowed
|
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
|
||||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
|
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -638,7 +620,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
|
|
||||||
if hasPushOptions {
|
if hasPushOptions {
|
||||||
for {
|
for {
|
||||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
|
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -735,8 +717,8 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
type pktLineType int64
|
type pktLineType int64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Unknown type
|
// UnKnow type
|
||||||
pktLineTypeUnknown pktLineType = 0
|
pktLineTypeUnknow pktLineType = 0
|
||||||
// flush-pkt "0000"
|
// flush-pkt "0000"
|
||||||
pktLineTypeFlush pktLineType = iota
|
pktLineTypeFlush pktLineType = iota
|
||||||
// data line
|
// data line
|
||||||
@@ -758,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)
|
||||||
|
|||||||
@@ -39,17 +39,3 @@ func TestPktLine(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []byte("0007a\nb"), w.Bytes())
|
assert.Equal(t, []byte("0007a\nb"), w.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseGitHookCommitRefLine(t *testing.T) {
|
|
||||||
oldCommitID, newCommitID, refName, ok := parseGitHookCommitRefLine("a b c")
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, "a", oldCommitID)
|
|
||||||
assert.Equal(t, "b", newCommitID)
|
|
||||||
assert.Equal(t, "c", string(refName))
|
|
||||||
|
|
||||||
_, _, _, ok = parseGitHookCommitRefLine("a\tb\tc")
|
|
||||||
assert.False(t, ok)
|
|
||||||
|
|
||||||
_, _, _, ok = parseGitHookCommitRefLine("a b")
|
|
||||||
assert.False(t, ok)
|
|
||||||
}
|
|
||||||
|
|||||||
77
cmd/keys.go
77
cmd/keys.go
@@ -4,7 +4,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -12,48 +11,45 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewKeysCommand returns the internal SSH key lookup sub-command.
|
// CmdKeys represents the available keys sub-command
|
||||||
func NewKeysCommand() *cli.Command {
|
var CmdKeys = &cli.Command{
|
||||||
return &cli.Command{
|
Name: "keys",
|
||||||
Name: "keys",
|
Usage: "(internal) Should only be called by SSH server",
|
||||||
Usage: "(internal) Should only be called by SSH server",
|
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
||||||
Hidden: true, // internal commands shouldn't be visible
|
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||||
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
Action: runKeys,
|
||||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
Flags: []cli.Flag{
|
||||||
Action: runKeys,
|
&cli.StringFlag{
|
||||||
Flags: []cli.Flag{
|
Name: "expected",
|
||||||
&cli.StringFlag{
|
Aliases: []string{"e"},
|
||||||
Name: "expected",
|
Value: "git",
|
||||||
Aliases: []string{"e"},
|
Usage: "Expected user for whom provide key commands",
|
||||||
Value: "git",
|
|
||||||
Usage: "Expected user for whom provide key commands",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "username",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "Username trying to log in by SSH",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "type",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "content",
|
|
||||||
Aliases: []string{"k"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "Username trying to log in by SSH",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "type",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "content",
|
||||||
|
Aliases: []string{"k"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
@@ -72,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)
|
||||||
@@ -79,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,29 @@
|
|||||||
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")
|
||||||
confirmSkipped := c.Bool("force")
|
confirmSkiped := c.Bool("force")
|
||||||
body := c.String("content")
|
body := c.String("content")
|
||||||
|
|
||||||
if !confirmSkipped {
|
if !confirmSkiped {
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
fmt.Print("warning: Content is empty")
|
fmt.Print("warning: Content is empty")
|
||||||
}
|
}
|
||||||
|
|||||||
223
cmd/main.go
223
cmd/main.go
@@ -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) {
|
if lineage[targetCmdIdx+1].Command != nil {
|
||||||
cmd, _ := data.(*cli.Command)
|
err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
|
||||||
if cmd != nil {
|
} else {
|
||||||
prepareWorkPathAndCustomConf(cmd)
|
err = cli.ShowAppHelp(c)
|
||||||
}
|
}
|
||||||
cliHelpPrinterOld(out, templ, data)
|
_, _ = fmt.Fprintf(c.App.Writer, `
|
||||||
if setting.CustomConf != "" {
|
|
||||||
_, _ = fmt.Fprintf(out, `
|
|
||||||
DEFAULT CONFIGURATION:
|
DEFAULT CONFIGURATION:
|
||||||
AppPath: %s
|
AppPath: %s
|
||||||
WorkPath: %s
|
WorkPath: %s
|
||||||
@@ -45,119 +41,136 @@ 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 {
|
||||||
|
prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
|
||||||
|
// 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(action cli.ActionFunc) func(ctx *cli.Context) error {
|
||||||
|
return func(ctx *cli.Context) error {
|
||||||
|
var args setting.ArgWorkPathAndCustomConf
|
||||||
|
// from children to parent, check the global flags
|
||||||
|
for _, curCtx := range ctx.Lineage() {
|
||||||
|
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||||
|
args.WorkPath = curCtx.String("work-path")
|
||||||
|
}
|
||||||
|
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
|
||||||
|
args.CustomPath = curCtx.String("custom-path")
|
||||||
|
}
|
||||||
|
if curCtx.IsSet("config") && args.CustomConf == "" {
|
||||||
|
args.CustomConf = curCtx.String("config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prepareWorkPathAndCustomConf(cmd)
|
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||||
return ctx, nil
|
if ctx.Bool("help") || action == nil {
|
||||||
|
// the default behavior of "urfave/cli": "nil action" means "show help"
|
||||||
|
return cmdHelp().Action(ctx)
|
||||||
|
}
|
||||||
|
return action(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareWorkPathAndCustomConf tries to prepare the work path, custom path and custom config from various inputs:
|
|
||||||
// command line flags, environment variables, config file
|
|
||||||
func prepareWorkPathAndCustomConf(cmd *cli.Command) {
|
|
||||||
var args setting.ArgWorkPathAndCustomConf
|
|
||||||
if cmd.IsSet("work-path") {
|
|
||||||
args.WorkPath = cmd.String("work-path")
|
|
||||||
}
|
|
||||||
if cmd.IsSet("custom-path") {
|
|
||||||
args.CustomPath = cmd.String("custom-path")
|
|
||||||
}
|
|
||||||
if cmd.IsSet("config") {
|
|
||||||
args.CustomConf = cmd.String("config")
|
|
||||||
}
|
|
||||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppVersion struct {
|
type AppVersion struct {
|
||||||
Version string
|
Version string
|
||||||
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')",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
webCmd := newWebCommand()
|
|
||||||
// these sub-commands need to use a config file
|
|
||||||
subCmdWithConfig := []*cli.Command{
|
subCmdWithConfig := []*cli.Command{
|
||||||
webCmd,
|
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
|
||||||
newServCommand(),
|
CmdWeb,
|
||||||
newHookCommand(),
|
CmdServ,
|
||||||
NewKeysCommand(),
|
CmdHook,
|
||||||
newDumpCommand(),
|
CmdKeys,
|
||||||
newAdminCommand(),
|
CmdDump,
|
||||||
newMigrateCommand(),
|
CmdAdmin,
|
||||||
newDoctorCommand(),
|
CmdMigrate,
|
||||||
newManagerCommand(),
|
CmdDoctor,
|
||||||
newEmbeddedCommand(),
|
CmdManager,
|
||||||
newMigrateStorageCommand(),
|
CmdEmbedded,
|
||||||
newDumpRepositoryCommand(),
|
CmdMigrateStorage,
|
||||||
newRestoreRepositoryCommand(),
|
CmdDumpRepository,
|
||||||
newActionsCommand(),
|
CmdRestoreRepository,
|
||||||
|
CmdActions,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{
|
||||||
cmdConfig(),
|
CmdCert,
|
||||||
cmdCert(),
|
CmdGenerate,
|
||||||
newGenerateCommand(),
|
CmdDocs,
|
||||||
newDocsCommand(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we should eventually drop the default command,
|
app.DefaultCommand = CmdWeb.Name
|
||||||
// but not sure whether it would break Windows users who used to double-click the EXE to run.
|
|
||||||
app.DefaultCommand = webCmd.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...)
|
||||||
|
|
||||||
setting.UnsetUnnecessaryEnvVars()
|
setting.InitGiteaEnvVars()
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
113
cmd/main_test.go
113
cmd/main_test.go
@@ -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,15 +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) {
|
||||||
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -171,63 +122,37 @@ func TestCliCmd(t *testing.T) {
|
|||||||
r, err := runTestApp(app, args...)
|
r, err := runTestApp(app, args...)
|
||||||
assert.NoError(t, err, c.cmd)
|
assert.NoError(t, err, c.cmd)
|
||||||
assert.NotEmpty(t, c.exp, c.cmd)
|
assert.NotEmpty(t, c.exp, c.cmd)
|
||||||
if !assert.Contains(t, r.Stdout, c.exp, c.cmd) {
|
assert.Contains(t, r.Stdout, c.exp, c.cmd)
|
||||||
t.Log("Full output:\n" + r.Stdout)
|
|
||||||
t.Log("Expected:\n" + c.exp)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"])
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,33 +4,30 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newManagerCommand() *cli.Command {
|
var (
|
||||||
return &cli.Command{
|
// CmdManager represents the manager command
|
||||||
|
CmdManager = &cli.Command{
|
||||||
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{
|
||||||
newShutdownCommand(),
|
subcmdShutdown,
|
||||||
newRestartCommand(),
|
subcmdRestart,
|
||||||
newReloadTemplatesCommand(),
|
subcmdReloadTemplates,
|
||||||
newFlushQueuesCommand(),
|
subcmdFlushQueues,
|
||||||
newLoggingCommand(),
|
subcmdLogging,
|
||||||
newProcessesCommand(),
|
subCmdProcesses,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
subcmdShutdown = &cli.Command{
|
||||||
|
|
||||||
func newShutdownCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "shutdown",
|
Name: "shutdown",
|
||||||
Usage: "Gracefully shutdown the running process",
|
Usage: "Gracefully shutdown the running process",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@@ -40,10 +37,7 @@ func newShutdownCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
Action: runShutdown,
|
Action: runShutdown,
|
||||||
}
|
}
|
||||||
}
|
subcmdRestart = &cli.Command{
|
||||||
|
|
||||||
func newRestartCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "restart",
|
Name: "restart",
|
||||||
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
|
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@@ -53,10 +47,7 @@ func newRestartCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
Action: runRestart,
|
Action: runRestart,
|
||||||
}
|
}
|
||||||
}
|
subcmdReloadTemplates = &cli.Command{
|
||||||
|
|
||||||
func newReloadTemplatesCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "reload-templates",
|
Name: "reload-templates",
|
||||||
Usage: "Reload template files in the running process",
|
Usage: "Reload template files in the running process",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@@ -66,10 +57,7 @@ func newReloadTemplatesCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
Action: runReloadTemplates,
|
Action: runReloadTemplates,
|
||||||
}
|
}
|
||||||
}
|
subcmdFlushQueues = &cli.Command{
|
||||||
|
|
||||||
func newFlushQueuesCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "flush-queues",
|
Name: "flush-queues",
|
||||||
Usage: "Flush queues in the running process",
|
Usage: "Flush queues in the running process",
|
||||||
Action: runFlushQueues,
|
Action: runFlushQueues,
|
||||||
@@ -88,10 +76,7 @@ func newFlushQueuesCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
subCmdProcesses = &cli.Command{
|
||||||
|
|
||||||
func newProcessesCommand() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "processes",
|
Name: "processes",
|
||||||
Usage: "Display running processes within the current process",
|
Usage: "Display running processes within the current process",
|
||||||
Action: runProcesses,
|
Action: runProcesses,
|
||||||
@@ -121,33 +106,48 @@ func newProcessesCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
|
func runShutdown(c *cli.Context) error {
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
func runShutdown(ctx context.Context, c *cli.Command) error {
|
|
||||||
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)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -12,11 +11,11 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultLoggingFlags() []cli.Flag {
|
var (
|
||||||
return []cli.Flag{
|
defaultLoggingFlags = []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "logger",
|
Name: "logger",
|
||||||
Usage: `Logger name - will default to "default"`,
|
Usage: `Logger name - will default to "default"`,
|
||||||
@@ -57,13 +56,11 @@ func defaultLoggingFlags() []cli.Flag {
|
|||||||
Name: "debug",
|
Name: "debug",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func newLoggingCommand() *cli.Command {
|
subcmdLogging = &cli.Command{
|
||||||
return &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)",
|
||||||
@@ -107,11 +104,11 @@ func newLoggingCommand() *cli.Command {
|
|||||||
}, {
|
}, {
|
||||||
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",
|
||||||
Flags: append(defaultLoggingFlags(), []cli.Flag{
|
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "filename",
|
Name: "filename",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
@@ -152,7 +149,7 @@ func newLoggingCommand() *cli.Command {
|
|||||||
}, {
|
}, {
|
||||||
Name: "conn",
|
Name: "conn",
|
||||||
Usage: "Add a net conn logger",
|
Usage: "Add a net conn logger",
|
||||||
Flags: append(defaultLoggingFlags(), []cli.Flag{
|
Flags: append(defaultLoggingFlags, []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "reconnect-on-message",
|
Name: "reconnect-on-message",
|
||||||
Aliases: []string{"R"},
|
Aliases: []string{"R"},
|
||||||
@@ -193,9 +190,12 @@ func newLoggingCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
|
func runRemoveLogger(c *cli.Context) error {
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
func runRemoveLogger(ctx context.Context, c *cli.Command) error {
|
|
||||||
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 {
|
||||||
@@ -207,7 +207,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"
|
||||||
@@ -231,10 +234,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"
|
||||||
@@ -261,10 +267,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()
|
||||||
}
|
}
|
||||||
@@ -291,33 +297,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"))
|
||||||
|
|||||||
@@ -11,20 +11,22 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newMigrateCommand() *cli.Command {
|
// CmdMigrate represents the available migrate sub-command.
|
||||||
return &cli.Command{
|
var CmdMigrate = &cli.Command{
|
||||||
Name: "migrate",
|
Name: "migrate",
|
||||||
Usage: "Migrate the database",
|
Usage: "Migrate the database",
|
||||||
Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
|
Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,111 +22,110 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newMigrateStorageCommand() *cli.Command {
|
// CmdMigrateStorage represents the available migrate storage sub-command.
|
||||||
return &cli.Command{
|
var CmdMigrateStorage = &cli.Command{
|
||||||
Name: "migrate-storage",
|
Name: "migrate-storage",
|
||||||
Usage: "Migrate the storage",
|
Usage: "Migrate the storage",
|
||||||
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
||||||
Action: runMigrateStorage,
|
Action: runMigrateStorage,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "type",
|
Name: "type",
|
||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'",
|
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts",
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "storage",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "New storage type: local (default), minio or azureblob",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "path",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Value: "",
|
|
||||||
Usage: "New storage placement if store is local (leave blank for default)",
|
|
||||||
},
|
|
||||||
// Minio Storage special configurations
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-endpoint",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage endpoint",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-access-key-id",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage accessKeyID",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-secret-access-key",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage secretAccessKey",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-bucket",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage bucket",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-location",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage location to create bucket",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-base-path",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio storage base path on the bucket",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "minio-use-ssl",
|
|
||||||
Usage: "Enable SSL for minio",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "minio-insecure-skip-verify",
|
|
||||||
Usage: "Skip SSL verification",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-checksum-algorithm",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio checksum algorithm (default/md5)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "minio-bucket-lookup-type",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Minio bucket lookup type",
|
|
||||||
},
|
|
||||||
// Azure Blob Storage special configurations
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "azureblob-endpoint",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Azure Blob storage endpoint",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "azureblob-account-name",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Azure Blob storage account name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "azureblob-account-key",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Azure Blob storage account key",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "azureblob-container",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Azure Blob storage container",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "azureblob-base-path",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Azure Blob storage base path",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "storage",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "New storage type: local (default), minio or azureblob",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "path",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Value: "",
|
||||||
|
Usage: "New storage placement if store is local (leave blank for default)",
|
||||||
|
},
|
||||||
|
// Minio Storage special configurations
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-endpoint",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage endpoint",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-access-key-id",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage accessKeyID",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-secret-access-key",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage secretAccessKey",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-bucket",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage bucket",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-location",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage location to create bucket",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-base-path",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio storage base path on the bucket",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "minio-use-ssl",
|
||||||
|
Usage: "Enable SSL for minio",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "minio-insecure-skip-verify",
|
||||||
|
Usage: "Skip SSL verification",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-checksum-algorithm",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio checksum algorithm (default/md5)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "minio-bucket-lookup-type",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Minio bucket lookup type",
|
||||||
|
},
|
||||||
|
// Azure Blob Storage special configurations
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "azureblob-endpoint",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Azure Blob storage endpoint",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "azureblob-account-name",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Azure Blob storage account name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "azureblob-account-key",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Azure Blob storage account key",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "azureblob-container",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Azure Blob storage container",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "azureblob-base-path",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Azure Blob storage base path",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||||
@@ -214,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,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
|
||||||
@@ -297,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"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -4,53 +4,54 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRestoreRepositoryCommand() *cli.Command {
|
// CmdRestoreRepository represents the available restore a repository sub-command.
|
||||||
return &cli.Command{
|
var CmdRestoreRepository = &cli.Command{
|
||||||
Name: "restore-repo",
|
Name: "restore-repo",
|
||||||
Usage: "Restore the repository from disk",
|
Usage: "Restore the repository from disk",
|
||||||
Description: "This is a command for restoring the repository data.",
|
Description: "This is a command for restoring the repository data.",
|
||||||
Action: runRestoreRepository,
|
Action: runRestoreRepository,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "repo_dir",
|
Name: "repo_dir",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Value: "./data",
|
Value: "./data",
|
||||||
Usage: "Repository dir path to restore from",
|
Usage: "Repository dir path to restore from",
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "owner_name",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Restore destination owner name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "repo_name",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Restore destination repository name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "units",
|
|
||||||
Value: "",
|
|
||||||
Usage: `Which items will be restored, one or more units should be separated as comma.
|
|
||||||
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "validation",
|
|
||||||
Usage: "Sanity check the content of the files before trying to load them",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.StringFlag{
|
||||||
|
Name: "owner_name",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Restore destination owner name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "repo_name",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Restore destination repository name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "units",
|
||||||
|
Value: "",
|
||||||
|
Usage: `Which items will be restored, one or more units should be separated as comma.
|
||||||
|
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "validation",
|
||||||
|
Usage: "Sanity check the content of the files before trying to load them",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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 != "" {
|
||||||
|
|||||||
115
cmd/serv.go
115
cmd/serv.go
@@ -13,14 +13,14 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"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/models/repo"
|
||||||
"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,27 +31,26 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newServCommand() *cli.Command {
|
// CmdServ represents the available serv sub-command.
|
||||||
return &cli.Command{
|
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{
|
&cli.BoolFlag{
|
||||||
&cli.BoolFlag{
|
Name: "enable-pprof",
|
||||||
Name: "enable-pprof",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "debug",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
&cli.BoolFlag{
|
||||||
|
Name: "debug",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(ctx context.Context, debug bool) {
|
func setup(ctx context.Context, debug bool) {
|
||||||
@@ -65,7 +64,7 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +131,31 @@ func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
|||||||
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"))
|
||||||
|
|
||||||
@@ -192,7 +215,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +231,12 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||||||
username := repoPathFields[0]
|
username := repoPathFields[0]
|
||||||
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
|
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
|
||||||
|
|
||||||
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 !repo.IsValidSSHAccessRepoName(reponame) {
|
||||||
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,16 +282,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 == git.CmdVerbLfsTransfer {
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -274,7 +295,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||||||
if verb == git.CmdVerbLfsAuthenticate {
|
if verb == git.CmdVerbLfsAuthenticate {
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -293,30 +314,30 @@ 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
|
||||||
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
|
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
|
||||||
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,
|
||||||
@@ -331,9 +352,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user