Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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]
|
||||
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
|
||||
cmd = "make --no-print-directory backend"
|
||||
entrypoint = ["./gitea"]
|
||||
bin = "gitea"
|
||||
delay = 2000
|
||||
include_ext = ["go", "tmpl"]
|
||||
include_file = ["main.go"]
|
||||
|
||||
+4
-1
@@ -37,7 +37,10 @@ groups:
|
||||
name: BUGFIXES
|
||||
labels:
|
||||
- type/bug
|
||||
|
||||
-
|
||||
name: API
|
||||
labels:
|
||||
- modifies/api
|
||||
-
|
||||
name: TESTING
|
||||
labels:
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
{
|
||||
"name": "Gitea DevContainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.26-trixie",
|
||||
"containerEnv": {
|
||||
// override "local" from packaged version
|
||||
"GOTOOLCHAIN": "auto"
|
||||
},
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
|
||||
"features": {
|
||||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "latest"
|
||||
"version": "20"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
|
||||
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"version": "3.14"
|
||||
"version": "3.12"
|
||||
},
|
||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
// same extensions as Gitpod, should match /.gitpod.yml
|
||||
"extensions": [
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
|
||||
+17
-4
@@ -36,11 +36,22 @@ _testmain.go
|
||||
coverage.all
|
||||
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
|
||||
*.log
|
||||
|
||||
/gitea
|
||||
/gitea-vet
|
||||
/debug
|
||||
/integrations.test
|
||||
|
||||
/bin
|
||||
/dist
|
||||
@@ -52,12 +63,17 @@ cpu.out
|
||||
/indexers
|
||||
/log
|
||||
/tests/integration/gitea-integration-*
|
||||
/tests/integration/indexers-*
|
||||
/tests/e2e/gitea-e2e-*
|
||||
/tests/e2e/indexers-*
|
||||
/tests/e2e/reports
|
||||
/tests/e2e/test-artifacts
|
||||
/tests/e2e/test-snapshots
|
||||
/tests/*.ini
|
||||
/node_modules
|
||||
/yarn.lock
|
||||
/yarn-error.log
|
||||
/npm-debug.log*
|
||||
/pnpm-debug.log*
|
||||
/public/assets/js
|
||||
/public/assets/css
|
||||
/public/assets/fonts
|
||||
@@ -66,9 +82,6 @@ cpu.out
|
||||
/VERSION
|
||||
/.air
|
||||
/.go-licenses
|
||||
/Dockerfile
|
||||
/Dockerfile.rootless
|
||||
/.venv
|
||||
|
||||
# Files and folders that were previously generated
|
||||
/public/assets/img/webpack
|
||||
|
||||
+1
-5
@@ -18,17 +18,13 @@ indent_style = tab
|
||||
[templates/custom/*.tmpl]
|
||||
insert_final_newline = false
|
||||
|
||||
[templates/swagger/*_json.tmpl]
|
||||
[templates/swagger/v1_json.tmpl]
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
|
||||
[templates/user/auth/oidc_wellknown.tmpl]
|
||||
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]
|
||||
indent_style = tab
|
||||
|
||||
|
||||
+1004
File diff suppressed because it is too large
Load Diff
@@ -8,4 +8,3 @@
|
||||
/vendor/** -text -eol linguist-vendored
|
||||
/web_src/js/vendor/** -text -eol linguist-vendored
|
||||
Dockerfile.* linguist-language=Dockerfile
|
||||
Makefile.* linguist-language=Makefile
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
name: docker-dryrun
|
||||
description: Composite action that performs the container build steps for a single platform.
|
||||
|
||||
inputs:
|
||||
platform:
|
||||
description: "The target platform: linux/amd64, linux/arm64, linux/riscv64."
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
- name: Build regular image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ inputs.platform }}
|
||||
push: false
|
||||
file: Dockerfile
|
||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
||||
- name: Build rootless image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ inputs.platform }}
|
||||
push: false
|
||||
file: Dockerfile.rootless
|
||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
|
||||
@@ -1,47 +0,0 @@
|
||||
name: go-caches
|
||||
description: Restore and save go module, build, and golangci-lint caches
|
||||
|
||||
inputs:
|
||||
cache-name:
|
||||
description: Short identifier used in the per-caller build cache key
|
||||
required: true
|
||||
build-cache:
|
||||
description: Whether to include ~/.cache/go-build
|
||||
default: "true"
|
||||
build-cache-rotate:
|
||||
description: Whether to rotate the build cache key per run so Go's test result cache can accumulate across runs
|
||||
default: "false"
|
||||
lint-cache:
|
||||
description: Whether to include ~/.cache/golangci-lint
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: gomod-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||
restore-keys: gomod-${{ runner.os }}-${{ runner.arch }}
|
||||
- if: ${{ inputs.build-cache == 'true' && inputs.build-cache-rotate == 'true' }}
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}-${{ hashFiles('go.sum') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
gobuild-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}-${{ hashFiles('go.sum') }}
|
||||
gobuild-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}
|
||||
gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||
gobuild-${{ runner.os }}-${{ runner.arch }}
|
||||
- if: ${{ inputs.build-cache == 'true' && inputs.build-cache-rotate != 'true' }}
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: gobuild-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('go.sum') }}
|
||||
restore-keys: gobuild-${{ runner.os }}-${{ runner.arch }}
|
||||
- if: ${{ inputs.lint-cache == 'true' }}
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.cache/golangci-lint
|
||||
key: golangci-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}-${{ hashFiles('go.sum', '.golangci.yml') }}
|
||||
restore-keys: golangci-${{ runner.os }}-${{ runner.arch }}-${{ inputs.cache-name }}
|
||||
+79
-10
@@ -1,14 +1,83 @@
|
||||
modifies/docs:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "**/*.md"
|
||||
- "docs/**"
|
||||
|
||||
modifies/templates:
|
||||
- changed-files:
|
||||
- all-globs-to-any-file:
|
||||
- "templates/**"
|
||||
- "!templates/swagger/v1_json.tmpl"
|
||||
|
||||
modifies/api:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "routers/api/**"
|
||||
- "templates/swagger/v1_json.tmpl"
|
||||
|
||||
modifies/cli:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "cmd/**"
|
||||
|
||||
modifies/translation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "options/locale/*.ini"
|
||||
|
||||
modifies/migrations:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "models/migrations/**"
|
||||
|
||||
modifies/internal:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ".air.toml"
|
||||
- "Makefile"
|
||||
- "Dockerfile"
|
||||
- "Dockerfile.rootless"
|
||||
- ".dockerignore"
|
||||
- "docker/**"
|
||||
- ".editorconfig"
|
||||
- ".eslintrc.cjs"
|
||||
- ".golangci.yml"
|
||||
- ".gitpod.yml"
|
||||
- ".markdownlint.yaml"
|
||||
- ".spectral.yaml"
|
||||
- "stylelint.config.js"
|
||||
- ".yamllint.yaml"
|
||||
- ".github/**"
|
||||
- ".gitea/**"
|
||||
- ".devcontainer/**"
|
||||
- "build.go"
|
||||
- "build/**"
|
||||
- "contrib/**"
|
||||
|
||||
modifies/dependencies:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
- "pyproject.toml"
|
||||
- "poetry.lock"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
|
||||
modifies/go:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "**/*.go"
|
||||
|
||||
modifies/frontend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "*.js"
|
||||
- "*.ts"
|
||||
- "web_src/**"
|
||||
|
||||
docs-update-needed:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "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.*"
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<!-- start tips -->
|
||||
Please check the following:
|
||||
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports.
|
||||
2. Use a Conventional Commits PR title, for example `fix(repo): handle empty branch names`.
|
||||
3. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
|
||||
4. For documentations contribution, please go to https://gitea.com/gitea/docs
|
||||
5. Describe what your pull request does and which issue you're targeting (if any).
|
||||
6. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
|
||||
7. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
|
||||
8. Delete all these tips before posting.
|
||||
2. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
|
||||
3. For documentations contribution, please go to https://gitea.com/gitea/docs
|
||||
4. Describe what your pull request does and which issue you're targeting (if any).
|
||||
5. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
|
||||
6. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
|
||||
7. Delete all these tips before posting.
|
||||
<!-- end tips -->
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# Populates the go module, build, and golangci-lint caches under the default
|
||||
# branch's cache scope so that PR runs have a warm fallback to restore from.
|
||||
#
|
||||
# GitHub Actions caches are scoped per ref: a PR run can only write to its own
|
||||
# branch's scope, but can read from the base branch's scope as a fallback.
|
||||
# PRs therefore cannot seed main's scope themselves. Running the same cache
|
||||
# steps on push-to-main is the only opportunity to populate that fallback
|
||||
# scope so fresh PR branches start with a useful cache on first run.
|
||||
|
||||
# A PR job's exact key lives in its own PR-scope (empty on first run, filled
|
||||
# by later runs of the same PR); on miss, actions/cache's restore-keys fall
|
||||
# back to prefix matches against entries this seeder saves in main's scope.
|
||||
|
||||
name: cache-seeder
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "go.sum"
|
||||
- ".golangci.yml"
|
||||
- ".github/actions/go-cache/action.yml"
|
||||
- ".github/workflows/cache-seeder.yml"
|
||||
|
||||
concurrency:
|
||||
group: cache-seeder
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
gobuild:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: seed
|
||||
- run: make deps-backend
|
||||
- run: TAGS="bindata" make backend
|
||||
- run: TAGS="bindata gogit" GOEXPERIMENT="" make backend
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { job: lint-backend, tags: "bindata", target: "lint-backend" }
|
||||
- { job: lint-go-windows, tags: "bindata", target: "lint-go-windows" }
|
||||
- { job: lint-go-gogit, tags: "bindata gogit", target: "lint-go" }
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: ${{ matrix.job }}
|
||||
lint-cache: "true"
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make ${{ matrix.target }}
|
||||
env:
|
||||
TAGS: ${{ matrix.tags }}
|
||||
@@ -9,18 +9,16 @@ jobs:
|
||||
cron-licenses:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'go-gitea/gitea'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- run: make generate-gitignore
|
||||
timeout-minutes: 40
|
||||
- name: push translations to repo
|
||||
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
|
||||
uses: appleboy/git-push-action@v0.0.3
|
||||
with:
|
||||
author_email: "teabot@gitea.io"
|
||||
author_name: GiteaBot
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
name: cron-renovate
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "23 * * * *" # hourly at :23
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: cron-renovate
|
||||
|
||||
env:
|
||||
RENOVATE_VERSION: 43.141.5 # renovate: datasource=docker depName=ghcr.io/renovatebot/renovate
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cron-renovate:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'go-gitea/gitea' # prevent running on forks
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: renovatebot/github-action@f66d8679fcfcfa051abde6e7a623007173bf5164 # v46.1.12
|
||||
with:
|
||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||
configurationFile: renovate.json5
|
||||
token: ${{ secrets.RENOVATE_TOKEN }}
|
||||
env:
|
||||
RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks.
|
||||
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg nolyfill)$"]'
|
||||
RENOVATE_REPOSITORIES: '["go-gitea/gitea"]'
|
||||
@@ -9,11 +9,9 @@ jobs:
|
||||
crowdin-pull:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'go-gitea/gitea'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
@@ -29,7 +27,7 @@ jobs:
|
||||
- name: update locales
|
||||
run: ./build/update-locales.sh
|
||||
- name: push translations to repo
|
||||
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
|
||||
uses: appleboy/git-push-action@v0.0.3
|
||||
with:
|
||||
author_email: "teabot@gitea.io"
|
||||
author_name: GiteaBot
|
||||
|
||||
@@ -15,19 +15,10 @@ on:
|
||||
value: ${{ jobs.detect.outputs.templates }}
|
||||
docker:
|
||||
value: ${{ jobs.detect.outputs.docker }}
|
||||
dockerfile:
|
||||
value: ${{ jobs.detect.outputs.dockerfile }}
|
||||
swagger:
|
||||
value: ${{ jobs.detect.outputs.swagger }}
|
||||
yaml:
|
||||
value: ${{ jobs.detect.outputs.yaml }}
|
||||
json:
|
||||
value: ${{ jobs.detect.outputs.json }}
|
||||
e2e:
|
||||
value: ${{ jobs.detect.outputs.e2e }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
detect:
|
||||
@@ -40,14 +31,11 @@ jobs:
|
||||
actions: ${{ steps.changes.outputs.actions }}
|
||||
templates: ${{ steps.changes.outputs.templates }}
|
||||
docker: ${{ steps.changes.outputs.docker }}
|
||||
dockerfile: ${{ steps.changes.outputs.dockerfile }}
|
||||
swagger: ${{ steps.changes.outputs.swagger }}
|
||||
yaml: ${{ steps.changes.outputs.yaml }}
|
||||
json: ${{ steps.changes.outputs.json }}
|
||||
e2e: ${{ steps.changes.outputs.e2e }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
@@ -60,7 +48,7 @@ jobs:
|
||||
- "Makefile"
|
||||
- ".golangci.yml"
|
||||
- ".editorconfig"
|
||||
- "options/locale/locale_en-US.json"
|
||||
- "options/locale/locale_en-US.ini"
|
||||
|
||||
frontend:
|
||||
- "*.js"
|
||||
@@ -70,7 +58,7 @@ jobs:
|
||||
- "tools/*.ts"
|
||||
- "assets/emoji.json"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "package-lock.json"
|
||||
- "Makefile"
|
||||
- ".eslintrc.cjs"
|
||||
- ".npmrc"
|
||||
@@ -79,7 +67,7 @@ jobs:
|
||||
- "**/*.md"
|
||||
- ".markdownlint.yaml"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "package-lock.json"
|
||||
|
||||
actions:
|
||||
- ".github/workflows/*"
|
||||
@@ -89,25 +77,20 @@ jobs:
|
||||
- "tools/lint-templates-*.js"
|
||||
- "templates/**/*.tmpl"
|
||||
- "pyproject.toml"
|
||||
- "uv.lock"
|
||||
- "poetry.lock"
|
||||
|
||||
docker:
|
||||
- ".github/workflows/pull-docker-dryrun.yml"
|
||||
- "Dockerfile"
|
||||
- "Dockerfile.rootless"
|
||||
- "docker/**"
|
||||
- "Makefile"
|
||||
|
||||
dockerfile:
|
||||
- "Dockerfile"
|
||||
- "Dockerfile.rootless"
|
||||
|
||||
swagger:
|
||||
- "templates/swagger/v1_json.tmpl"
|
||||
- "templates/swagger/v1_input.json"
|
||||
- "Makefile"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "package-lock.json"
|
||||
- ".spectral.yaml"
|
||||
|
||||
yaml:
|
||||
@@ -115,11 +98,4 @@ jobs:
|
||||
- "**/*.yaml"
|
||||
- ".yamllint.yaml"
|
||||
- "pyproject.toml"
|
||||
|
||||
json:
|
||||
- "**/*.json"
|
||||
|
||||
e2e:
|
||||
- "tests/e2e/**"
|
||||
- "tools/test-e2e.sh"
|
||||
- "playwright.config.ts"
|
||||
- "poetry.lock"
|
||||
|
||||
@@ -7,9 +7,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
@@ -19,70 +16,88 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: lint-backend
|
||||
lint-cache: "true"
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make lint-backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
|
||||
lint-on-demand:
|
||||
lint-templates:
|
||||
if: needs.files-changed.outputs.templates == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: pip install poetry
|
||||
- run: make deps-py
|
||||
- run: make deps-frontend
|
||||
- run: make lint-templates
|
||||
|
||||
lint-yaml:
|
||||
if: needs.files-changed.outputs.yaml == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- run: pip install poetry
|
||||
- run: make deps-py
|
||||
- run: make lint-yaml
|
||||
|
||||
lint-swagger:
|
||||
if: needs.files-changed.outputs.swagger == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: make deps-frontend
|
||||
- run: make lint-swagger
|
||||
|
||||
lint-spell:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
|
||||
- run: make lint-spell
|
||||
|
||||
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
||||
run: uv python install 3.14 && make deps-py lint-templates lint-yaml
|
||||
|
||||
- if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.swagger == 'true' || needs.files-changed.outputs.json == 'true'
|
||||
run: make deps-frontend lint-md lint-swagger lint-json
|
||||
|
||||
- if: needs.files-changed.outputs.actions == 'true'
|
||||
run: make lint-actions
|
||||
|
||||
lint-go-windows:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: lint-go-windows
|
||||
lint-cache: "true"
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make lint-go-windows
|
||||
- run: make lint-go-windows lint-go-gitea-vet
|
||||
env:
|
||||
TAGS: bindata
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
GOOS: windows
|
||||
GOARCH: amd64
|
||||
|
||||
@@ -91,36 +106,26 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: lint-go-gogit
|
||||
lint-cache: "true"
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make lint-go
|
||||
env:
|
||||
TAGS: bindata gogit
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
|
||||
checks-backend:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: checks-backend
|
||||
build-cache: "false"
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
||||
|
||||
@@ -129,13 +134,12 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
@@ -147,20 +151,17 @@ jobs:
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: compliance-backend
|
||||
- run: make deps-backend generate-go
|
||||
# no frontend build here as backend should be able to build, even without any frontend files
|
||||
# CGO is not used when cross-compile, so these steps also test if the code is compatible with CGO disabled
|
||||
# no frontend build here as backend should be able to build
|
||||
# even without any frontend files
|
||||
- run: make deps-backend
|
||||
- run: go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
|
||||
- name: build-backend-arm64
|
||||
run: go build -o gitea_linux_arm64
|
||||
run: make backend # test cross compile
|
||||
env:
|
||||
GOOS: linux
|
||||
GOARCH: arm64
|
||||
@@ -172,7 +173,33 @@ jobs:
|
||||
GOARCH: amd64
|
||||
TAGS: bindata gogit
|
||||
- name: build-backend-386
|
||||
run: go build -o gitea_linux_386
|
||||
run: go build -o gitea_linux_386 # test if compatible with 32 bit
|
||||
env:
|
||||
GOOS: linux
|
||||
GOARCH: 386
|
||||
|
||||
docs:
|
||||
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: make deps-frontend
|
||||
- run: make lint-md
|
||||
|
||||
actions:
|
||||
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- run: make lint-actions
|
||||
|
||||
@@ -7,9 +7,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
@@ -34,22 +31,18 @@ jobs:
|
||||
minio:
|
||||
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
||||
# that has a custom entrypoint set to "minio server /data"
|
||||
image: bitnamilegacy/minio:2023.12.23
|
||||
image: bitnami/minio:2023.8.31
|
||||
env:
|
||||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
ports:
|
||||
- "9000:9000"
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: pgsql
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
|
||||
- run: make deps-backend
|
||||
@@ -57,47 +50,41 @@ jobs:
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: run migration tests
|
||||
run: GITEA_TEST_DATABASE=pgsql make test-migration
|
||||
run: make test-pgsql-migration
|
||||
- name: run tests
|
||||
run: GITEA_TEST_DATABASE=pgsql make test-integration
|
||||
run: make test-pgsql
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
|
||||
GOTEST_FLAGS: -race -timeout=40m
|
||||
TAGS: bindata gogit
|
||||
RACE_ENABLED: true
|
||||
TEST_TAGS: gogit
|
||||
TEST_LDAP: 1
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
||||
test-sqlite:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: sqlite
|
||||
- run: make deps-backend
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata gogit
|
||||
GOEXPERIMENT:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
- name: run migration tests
|
||||
run: GITEA_TEST_DATABASE=sqlite make test-migration
|
||||
env:
|
||||
TAGS: bindata gogit
|
||||
run: make test-sqlite-migration
|
||||
- name: run tests
|
||||
run: GITEA_TEST_DATABASE=sqlite make test-integration
|
||||
run: make test-sqlite
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
|
||||
GOTEST_FLAGS: -timeout=40m
|
||||
TAGS: bindata gogit
|
||||
GOEXPERIMENT:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
RACE_ENABLED: true
|
||||
TEST_TAGS: gogit sqlite sqlite_unlock_notify
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
||||
test-unit:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
@@ -105,10 +92,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
|
||||
image: elasticsearch:7.5.0
|
||||
env:
|
||||
discovery.type: single-node
|
||||
xpack.security.enabled: false
|
||||
ports:
|
||||
- "9200:9200"
|
||||
meilisearch:
|
||||
@@ -127,7 +113,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
minio:
|
||||
image: bitnamilegacy/minio:2021.12.29
|
||||
image: bitnami/minio:2021.3.17
|
||||
env:
|
||||
MINIO_ACCESS_KEY: 123456
|
||||
MINIO_SECRET_KEY: 12345678
|
||||
@@ -138,16 +124,11 @@ jobs:
|
||||
ports:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: unit
|
||||
build-cache-rotate: "true"
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
|
||||
- run: make deps-backend
|
||||
@@ -155,17 +136,16 @@ jobs:
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: unit-tests
|
||||
run: make test-backend test-check
|
||||
run: make unit-test-coverage test-check
|
||||
env:
|
||||
GOTEST_FLAGS: -race -timeout=20m
|
||||
TAGS: bindata
|
||||
RACE_ENABLED: true
|
||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||
- name: unit-tests-gogit
|
||||
run: make test-backend test-check
|
||||
run: make unit-test-coverage test-check
|
||||
env:
|
||||
GOTEST_FLAGS: -race -timeout=20m
|
||||
TAGS: bindata gogit
|
||||
GOEXPERIMENT:
|
||||
RACE_ENABLED: true
|
||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||
|
||||
test-mysql:
|
||||
@@ -175,7 +155,7 @@ jobs:
|
||||
services:
|
||||
mysql:
|
||||
# the bitnami mysql image has more options than the official one, it's easier to customize
|
||||
image: bitnamilegacy/mysql:8.4
|
||||
image: bitnami/mysql:8.0
|
||||
env:
|
||||
ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: testgitea
|
||||
@@ -184,10 +164,9 @@ jobs:
|
||||
options: >-
|
||||
--mount type=tmpfs,destination=/bitnami/mysql/data
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
|
||||
image: elasticsearch:7.5.0
|
||||
env:
|
||||
discovery.type: single-node
|
||||
xpack.security.enabled: false
|
||||
ports:
|
||||
- "9200:9200"
|
||||
smtpimap:
|
||||
@@ -198,15 +177,11 @@ jobs:
|
||||
- "587:587"
|
||||
- "993:993"
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: mysql
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
|
||||
- run: make deps-backend
|
||||
@@ -214,11 +189,14 @@ jobs:
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: run migration tests
|
||||
run: GITEA_TEST_DATABASE=mysql make test-migration
|
||||
run: make test-mysql-migration
|
||||
- name: run tests
|
||||
run: GITEA_TEST_DATABASE=mysql make test-integration
|
||||
# run: make integration-test-coverage (at the moment, no coverage is really handled)
|
||||
run: make test-mysql
|
||||
env:
|
||||
TAGS: bindata
|
||||
RACE_ENABLED: true
|
||||
USE_REPO_TEST_DIR: 1
|
||||
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
||||
|
||||
test-mssql:
|
||||
@@ -239,24 +217,21 @@ jobs:
|
||||
ports:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
with:
|
||||
cache-name: mssql
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
|
||||
- run: make deps-backend
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- run: GITEA_TEST_DATABASE=mssql make test-migration
|
||||
- run: make test-mssql-migration
|
||||
- name: run tests
|
||||
run: GITEA_TEST_DATABASE=mssql make test-integration
|
||||
run: make test-mssql
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
||||
@@ -7,41 +7,29 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
|
||||
# QEMU-based build is slow (40-50 minutes), so run arm64 and riscv64 when dockerfile changes.
|
||||
# Run amd64 when any docker-related files change, which is fast (4 minutes).
|
||||
container-amd64:
|
||||
if: needs.files-changed.outputs.docker == 'true'
|
||||
needs: [files-changed]
|
||||
regular:
|
||||
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/docker-dryrun
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v5
|
||||
with:
|
||||
platform: linux/amd64
|
||||
push: false
|
||||
tags: gitea/gitea:linux-amd64
|
||||
|
||||
container-arm64:
|
||||
if: needs.files-changed.outputs.dockerfile == 'true'
|
||||
needs: [files-changed]
|
||||
rootless:
|
||||
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/docker-dryrun
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v5
|
||||
with:
|
||||
platform: linux/arm64
|
||||
|
||||
container-riscv64:
|
||||
if: needs.files-changed.outputs.dockerfile == 'true'
|
||||
needs: [files-changed]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/docker-dryrun
|
||||
with:
|
||||
platform: linux/riscv64
|
||||
push: false
|
||||
file: Dockerfile.rootless
|
||||
tags: gitea/gitea:linux-amd64
|
||||
|
||||
@@ -7,44 +7,30 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
uses: ./.github/workflows/files-changed.yml
|
||||
|
||||
test-e2e:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.e2e == '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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- uses: ./.github/actions/go-cache
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache-name: e2e
|
||||
build-cache: "false"
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
- run: make deps-frontend
|
||||
- run: make frontend
|
||||
- run: make deps-backend
|
||||
- run: make backend
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: make deps-frontend frontend deps-backend
|
||||
- run: npx playwright install --with-deps
|
||||
- run: make test-e2e-sqlite
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
TAGS: bindata
|
||||
- run: make playwright
|
||||
- run: make test-e2e
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
TAGS: bindata
|
||||
FORCE_COLOR: 1
|
||||
GITEA_TEST_E2E_DEBUG: 1
|
||||
USE_REPO_TEST_DIR: 1
|
||||
|
||||
@@ -15,6 +15,6 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
name: pr-title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint-pr-title:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- run: make lint-pr-title
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
@@ -11,31 +11,28 @@ concurrency:
|
||||
jobs:
|
||||
nightly-binary:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: make deps-frontend deps-backend
|
||||
# xgo build
|
||||
- run: make release
|
||||
env:
|
||||
TAGS: bindata
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
- name: import gpg key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||
@@ -52,7 +49,7 @@ jobs:
|
||||
echo "Cleaned name is ${REF_NAME}"
|
||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||
- name: configure aws
|
||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
@@ -60,76 +57,98 @@ jobs:
|
||||
- name: upload binaries to s3
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
|
||||
nightly-container:
|
||||
nightly-docker-rootful:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- 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@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
- 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"
|
||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
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@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
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
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GHCR using PAT
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: build regular docker image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
- name: fetch go modules
|
||||
run: make vendor
|
||||
- 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 }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
|
||||
tags: |-
|
||||
gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||
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
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
|
||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless,mode=max
|
||||
tags: |-
|
||||
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
|
||||
|
||||
@@ -12,31 +12,28 @@ concurrency:
|
||||
jobs:
|
||||
binary:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: make deps-frontend deps-backend
|
||||
# xgo build
|
||||
- run: make release
|
||||
env:
|
||||
TAGS: bindata
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
- name: import gpg key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||
@@ -53,7 +50,7 @@ jobs:
|
||||
echo "Cleaned name is ${REF_NAME}"
|
||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||
- name: configure aws
|
||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
@@ -62,7 +59,7 @@ jobs:
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
- name: Install GH CLI
|
||||
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
|
||||
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
|
||||
with:
|
||||
gh-cli-version: 2.39.1
|
||||
- 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/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
container:
|
||||
docker-rootful:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- 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@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
with:
|
||||
images: |-
|
||||
@@ -94,10 +89,38 @@ jobs:
|
||||
# 1.2.3-rc0
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
id: meta_rootless
|
||||
- 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: 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:
|
||||
images: |-
|
||||
gitea/gitea
|
||||
@@ -109,33 +132,23 @@ jobs:
|
||||
# 1.2.3-rc0
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GHCR using PAT
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: build regular container image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
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@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
- name: build rootless docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
@@ -15,31 +15,29 @@ jobs:
|
||||
binary:
|
||||
runs-on: namespace-profile-gitea-release-binary
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: pnpm-lock.yaml
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
- run: make deps-frontend deps-backend
|
||||
# xgo build
|
||||
- run: make release
|
||||
env:
|
||||
TAGS: bindata
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
- name: import gpg key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||
@@ -56,7 +54,7 @@ jobs:
|
||||
echo "Cleaned name is ${REF_NAME}"
|
||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||
- name: configure aws
|
||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
@@ -65,7 +63,7 @@ jobs:
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
- name: Install GH CLI
|
||||
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
|
||||
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
|
||||
with:
|
||||
gh-cli-version: 2.39.1
|
||||
- name: create github release
|
||||
@@ -73,20 +71,18 @@ jobs:
|
||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
container:
|
||||
docker-rootful:
|
||||
runs-on: namespace-profile-gitea-release-docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # to publish to ghcr.io
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- 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@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
with:
|
||||
images: |-
|
||||
@@ -101,10 +97,36 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
id: meta_rootless
|
||||
- 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: 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:
|
||||
images: |-
|
||||
gitea/gitea
|
||||
@@ -121,33 +143,23 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
annotations: |
|
||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GHCR using PAT
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: build regular container image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
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@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
- name: build rootless docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
+10
-24
@@ -22,12 +22,6 @@ _test
|
||||
.vscode
|
||||
__debug_bin*
|
||||
|
||||
# Visual Studio
|
||||
/.vs/
|
||||
|
||||
# mise version managment tool
|
||||
mise.toml
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
@@ -55,7 +49,9 @@ cpu.out
|
||||
*.log.*.gz
|
||||
|
||||
/gitea
|
||||
/gitea-vet
|
||||
/debug
|
||||
/integrations.test
|
||||
|
||||
/bin
|
||||
/dist
|
||||
@@ -65,9 +61,13 @@ cpu.out
|
||||
/indexers
|
||||
/log
|
||||
/public/assets/img/avatar
|
||||
/tests/e2e-output
|
||||
/tests/integration/gitea-integration-*
|
||||
/tests/integration/indexers-*
|
||||
/tests/e2e/gitea-e2e-*
|
||||
/tests/e2e/indexers-*
|
||||
/tests/e2e/reports
|
||||
/tests/e2e/test-artifacts
|
||||
/tests/e2e/test-snapshots
|
||||
/tests/*.ini
|
||||
/tests/**/*.git/**/*.sample
|
||||
/node_modules
|
||||
@@ -75,8 +75,6 @@ cpu.out
|
||||
/yarn.lock
|
||||
/yarn-error.log
|
||||
/npm-debug.log*
|
||||
/.pnpm-store
|
||||
/public/assets/.vite
|
||||
/public/assets/js
|
||||
/public/assets/css
|
||||
/public/assets/fonts
|
||||
@@ -84,7 +82,10 @@ cpu.out
|
||||
/vendor
|
||||
/VERSION
|
||||
/.air
|
||||
/.go-licenses
|
||||
|
||||
# Files and folders that were previously generated
|
||||
/public/assets/img/webpack
|
||||
|
||||
# Snapcraft
|
||||
/gitea_a*.txt
|
||||
@@ -105,18 +106,3 @@ prime/
|
||||
|
||||
# Manpage
|
||||
/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
@@ -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
|
||||
+23
-47
@@ -6,20 +6,15 @@ linters:
|
||||
default: none
|
||||
enable:
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dupl
|
||||
- errcheck
|
||||
- forbidigo
|
||||
- gocheckcompilerdirectives
|
||||
- gocritic
|
||||
- goheader
|
||||
- govet
|
||||
- ineffassign
|
||||
- mirror
|
||||
- modernize
|
||||
- nakedret
|
||||
- nilnil
|
||||
- nolintlint
|
||||
- perfsprint
|
||||
- revive
|
||||
@@ -50,54 +45,44 @@ linters:
|
||||
desc: do not use the ini package, use gitea's config system instead
|
||||
- pkg: gitea.com/go-chi/cache
|
||||
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:
|
||||
enabled-checks:
|
||||
- equalFold
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
|
||||
- deprecatedComment # conflicts with go-swagger comments
|
||||
revive:
|
||||
severity: error
|
||||
rules:
|
||||
- name: atomic
|
||||
- name: bare-return
|
||||
- name: blank-imports
|
||||
- name: constant-logical-expr
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: duplicated-imports
|
||||
- name: empty-lines
|
||||
- name: error-naming
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: errorf
|
||||
- name: exported
|
||||
- name: identical-branches
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: indent-error-flow
|
||||
- name: modifies-value-receiver
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: redefines-builtin-id
|
||||
- name: string-of-int
|
||||
- name: superfluous-else
|
||||
- name: time-naming
|
||||
- name: unconditional-recursion
|
||||
- name: unexported-return
|
||||
- name: unreachable-code
|
||||
- name: var-declaration
|
||||
- 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:
|
||||
checks:
|
||||
- all
|
||||
@@ -112,17 +97,6 @@ linters:
|
||||
- require-error
|
||||
usetesting:
|
||||
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:
|
||||
generated: lax
|
||||
presets:
|
||||
@@ -134,12 +108,16 @@ linters:
|
||||
- linters:
|
||||
- dupl
|
||||
- errcheck
|
||||
- gocyclo
|
||||
- gosec
|
||||
- staticcheck
|
||||
- unparam
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- dupl
|
||||
- errcheck
|
||||
- gocyclo
|
||||
- gosec
|
||||
path: models/migrations/v
|
||||
- linters:
|
||||
- forbidigo
|
||||
@@ -151,8 +129,12 @@ linters:
|
||||
- gocritic
|
||||
text: (?i)`ID' should not be capitalized
|
||||
- linters:
|
||||
- deadcode
|
||||
- unused
|
||||
text: (?i)swagger
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: (?i)argument x is overwritten before first use
|
||||
- linters:
|
||||
- gocritic
|
||||
text: '(?i)commentFormatting: put a space between `//` and comment text'
|
||||
@@ -161,7 +143,6 @@ linters:
|
||||
text: '(?i)exitAfterDefer:'
|
||||
paths:
|
||||
- node_modules
|
||||
- .venv
|
||||
- public
|
||||
- web_src
|
||||
- third_party$
|
||||
@@ -172,25 +153,20 @@ issues:
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
settings:
|
||||
gci:
|
||||
custom-order: true
|
||||
sections:
|
||||
- standard
|
||||
- prefix(code.gitea.io/gitea)
|
||||
- blank
|
||||
- default
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- node_modules
|
||||
- .venv
|
||||
- public
|
||||
- web_src
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
run:
|
||||
timeout: 10m
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
*.min.css
|
||||
*.min.js
|
||||
/assets/*.json
|
||||
/modules/options/bindata.go
|
||||
/modules/public/bindata.go
|
||||
/modules/templates/bindata.go
|
||||
/options/gitignore
|
||||
/options/license
|
||||
/public/assets
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"ssh": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"A:/ssh-mcp/src/index.js"
|
||||
]
|
||||
},
|
||||
"wiki": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"A:/wiki-mcp/dist/index.js"
|
||||
]
|
||||
},
|
||||
"project": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"A:/project-mcp/dist/index.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
name: Architecture Decision Record (ADR)
|
||||
about: Propose or document an architectural decision
|
||||
title: '[ADR] '
|
||||
labels: 'architecture, decision'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ADR Number
|
||||
ADR-XXXX
|
||||
|
||||
## Status
|
||||
- [ ] Proposed
|
||||
- [ ] Accepted
|
||||
- [ ] Deprecated
|
||||
- [ ] Superseded by ADR-XXXX
|
||||
|
||||
## Context
|
||||
Describe the issue or problem that motivates this decision.
|
||||
|
||||
## Decision
|
||||
State the architecture decision and provide rationale.
|
||||
|
||||
## Consequences
|
||||
### Positive
|
||||
- List positive consequences
|
||||
|
||||
### Negative
|
||||
- List negative consequences or trade-offs
|
||||
|
||||
### Neutral
|
||||
- List neutral aspects
|
||||
|
||||
## Alternatives Considered
|
||||
### Alternative 1
|
||||
- Description
|
||||
- Pros
|
||||
- Cons
|
||||
- Why not chosen
|
||||
|
||||
### Alternative 2
|
||||
- Description
|
||||
- Pros
|
||||
- Cons
|
||||
- Why not chosen
|
||||
|
||||
## Implementation Plan
|
||||
1. Step 1
|
||||
2. Step 2
|
||||
3. Step 3
|
||||
|
||||
## Stakeholders
|
||||
- **Decision Makers**: @user1, @user2
|
||||
- **Consulted**: @user3, @user4
|
||||
- **Informed**: team-name
|
||||
|
||||
## Technical Details
|
||||
### Architecture Diagram
|
||||
```
|
||||
[Add diagram or link]
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
- Dependency 1
|
||||
- Dependency 2
|
||||
|
||||
### Impact Analysis
|
||||
- **Performance**: [Impact description]
|
||||
- **Security**: [Impact description]
|
||||
- **Scalability**: [Impact description]
|
||||
- **Maintainability**: [Impact description]
|
||||
|
||||
## Testing Strategy
|
||||
- [ ] Unit tests
|
||||
- [ ] Integration tests
|
||||
- [ ] Performance tests
|
||||
- [ ] Security tests
|
||||
|
||||
## Documentation
|
||||
- [ ] Architecture documentation updated
|
||||
- [ ] API documentation updated
|
||||
- [ ] Developer guide updated
|
||||
- [ ] Runbook created
|
||||
|
||||
## Migration Path
|
||||
Describe how to migrate from current state to new architecture.
|
||||
|
||||
## Rollback Plan
|
||||
Describe how to rollback if issues occur.
|
||||
|
||||
## Timeline
|
||||
- **Proposal Date**:
|
||||
- **Decision Date**:
|
||||
- **Implementation Start**:
|
||||
- **Expected Completion**:
|
||||
|
||||
## References
|
||||
- Related ADRs:
|
||||
- External resources:
|
||||
- RFCs:
|
||||
|
||||
## Review Checklist
|
||||
- [ ] Aligns with enterprise architecture principles
|
||||
- [ ] Security implications reviewed
|
||||
- [ ] Performance implications reviewed
|
||||
- [ ] Cost implications reviewed
|
||||
- [ ] Compliance requirements met
|
||||
- [ ] Team consensus achieved
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or issue with the project
|
||||
title: '[BUG] '
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Bug Description
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
A clear and concise description of what actually happened.
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## Environment
|
||||
- **Project**: [e.g., MokoDoliTools, moko-cassiopeia]
|
||||
- **Version**: [e.g., 1.2.3]
|
||||
- **Platform**: [e.g., Dolibarr 18.0, Joomla 5.0]
|
||||
- **PHP Version**: [e.g., 8.1]
|
||||
- **Database**: [e.g., MySQL 8.0, PostgreSQL 14]
|
||||
- **Browser** (if applicable): [e.g., Chrome 120, Firefox 121]
|
||||
- **OS**: [e.g., Ubuntu 22.04, Windows 11]
|
||||
|
||||
## Additional Context
|
||||
Add any other context about the problem here.
|
||||
|
||||
## Possible Solution
|
||||
If you have suggestions on how to fix the issue, please describe them here.
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar issues before creating this one
|
||||
- [ ] I have provided all the requested information
|
||||
- [ ] I have tested this on the latest stable version
|
||||
- [ ] I have checked the documentation and couldn't find a solution
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 💼 Enterprise Support
|
||||
url: https://mokoconsulting.tech/enterprise
|
||||
about: Enterprise-level support and consultation services
|
||||
- name: 💬 Ask a Question
|
||||
url: https://mokoconsulting.tech/
|
||||
about: Get help or ask questions through our website
|
||||
- name: 📚 MokoStandards Documentation
|
||||
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
about: View our coding standards and best practices
|
||||
- name: 🔒 Report a Security Vulnerability
|
||||
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
|
||||
about: Report security vulnerabilities privately (for critical issues)
|
||||
- name: 💡 Community Discussions
|
||||
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
||||
about: Join community discussions and Q&A
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
name: Documentation Issue
|
||||
about: Report an issue with documentation
|
||||
title: '[DOCS] '
|
||||
labels: 'documentation'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Documentation Issue
|
||||
|
||||
**Location**:
|
||||
<!-- Specify the file, page, or section with the issue -->
|
||||
|
||||
## Issue Type
|
||||
<!-- Mark the relevant option with an "x" -->
|
||||
- [ ] Typo or grammar error
|
||||
- [ ] Outdated information
|
||||
- [ ] Missing documentation
|
||||
- [ ] Unclear explanation
|
||||
- [ ] Broken links
|
||||
- [ ] Missing examples
|
||||
- [ ] Other (specify below)
|
||||
|
||||
## Description
|
||||
<!-- Clearly describe the documentation issue -->
|
||||
|
||||
## Current Content
|
||||
<!-- Quote or describe the current documentation (if applicable) -->
|
||||
```
|
||||
Current text here
|
||||
```
|
||||
|
||||
## Suggested Improvement
|
||||
<!-- Provide your suggestion for how to improve the documentation -->
|
||||
```
|
||||
Suggested text here
|
||||
```
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context, screenshots, or references -->
|
||||
|
||||
## Standards Alignment
|
||||
- [ ] Follows MokoStandards documentation guidelines
|
||||
- [ ] Uses en_US/en_GB localization
|
||||
- [ ] Includes proper SPDX headers where applicable
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar documentation issues
|
||||
- [ ] I have provided a clear description
|
||||
- [ ] I have suggested an improvement (if applicable)
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature or enhancement
|
||||
title: '[FEATURE] '
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Feature Description
|
||||
A clear and concise description of the feature you'd like to see.
|
||||
|
||||
## Problem or Use Case
|
||||
Describe the problem this feature would solve or the use case it addresses.
|
||||
Ex. I'm always frustrated when [...]
|
||||
|
||||
## Proposed Solution
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
## Alternative Solutions
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
## Benefits
|
||||
Describe how this feature would benefit users:
|
||||
- Who would use this feature?
|
||||
- What problems does it solve?
|
||||
- What value does it add?
|
||||
|
||||
## Implementation Details (Optional)
|
||||
If you have ideas about how this could be implemented, share them here:
|
||||
- Technical approach
|
||||
- Files/components that might need changes
|
||||
- Any concerns or challenges you foresee
|
||||
|
||||
## Additional Context
|
||||
Add any other context, mockups, or screenshots about the feature request here.
|
||||
|
||||
## Relevant Standards
|
||||
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||
- [ ] Accessibility (WCAG 2.1 AA)
|
||||
- [ ] Localization (en_US/en_GB)
|
||||
- [ ] Security best practices
|
||||
- [ ] Code quality standards
|
||||
- [ ] Other: [specify]
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar feature requests before creating this one
|
||||
- [ ] I have clearly described the use case and benefits
|
||||
- [ ] I have considered alternative solutions
|
||||
- [ ] This feature aligns with the project's goals and scope
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about usage, features, or best practices
|
||||
title: '[QUESTION] '
|
||||
labels: ['question']
|
||||
assignees: ['jmiller']
|
||||
---
|
||||
|
||||
|
||||
## Question
|
||||
|
||||
**Your question:**
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
**What are you trying to accomplish?**
|
||||
|
||||
|
||||
**What have you already tried?**
|
||||
|
||||
|
||||
**Category**:
|
||||
- [ ] Script usage
|
||||
- [ ] Configuration
|
||||
- [ ] Workflow setup
|
||||
- [ ] Documentation interpretation
|
||||
- [ ] Best practices
|
||||
- [ ] Integration
|
||||
- [ ] Other: __________
|
||||
|
||||
## Environment (if relevant)
|
||||
|
||||
**Your setup**:
|
||||
- Operating System:
|
||||
- Version:
|
||||
|
||||
## What You've Researched
|
||||
|
||||
**Documentation reviewed**:
|
||||
- [ ] README.md
|
||||
- [ ] Project documentation
|
||||
- [ ] Other (specify): __________
|
||||
|
||||
**Similar issues/questions found**:
|
||||
- #
|
||||
- #
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
**What result are you hoping for?**
|
||||
|
||||
|
||||
## Code/Configuration Samples
|
||||
|
||||
**Relevant code or configuration** (if applicable):
|
||||
|
||||
```bash
|
||||
# Your code here
|
||||
```
|
||||
|
||||
## Additional Context
|
||||
|
||||
**Any other relevant information:**
|
||||
|
||||
|
||||
**Screenshots** (if helpful):
|
||||
|
||||
|
||||
## Urgency
|
||||
|
||||
- [ ] Urgent (blocking work)
|
||||
- [ ] Normal (can work on other things meanwhile)
|
||||
- [ ] Low priority (just curious)
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched existing issues and discussions
|
||||
- [ ] I have reviewed relevant documentation
|
||||
- [ ] I have provided sufficient context
|
||||
- [ ] I have included code/configuration samples if relevant
|
||||
- [ ] This is a genuine question (not a bug report or feature request)
|
||||
@@ -1,126 +0,0 @@
|
||||
---
|
||||
name: Request for Comments (RFC)
|
||||
about: Propose a significant change for community discussion
|
||||
title: '[RFC] '
|
||||
labels: 'rfc, discussion'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## RFC Summary
|
||||
One-paragraph summary of the proposal.
|
||||
|
||||
## Motivation
|
||||
Why are we doing this? What use cases does it support? What is the expected outcome?
|
||||
|
||||
## Detailed Design
|
||||
### Overview
|
||||
Provide a detailed explanation of the proposed change.
|
||||
|
||||
### API Changes (if applicable)
|
||||
```php
|
||||
// Before
|
||||
function oldApi($param1) { }
|
||||
|
||||
// After
|
||||
function newApi($param1, $param2) { }
|
||||
```
|
||||
|
||||
### User Experience Changes
|
||||
Describe how users will interact with this change.
|
||||
|
||||
### Implementation Approach
|
||||
High-level implementation strategy.
|
||||
|
||||
## Drawbacks
|
||||
Why should we *not* do this?
|
||||
|
||||
## Alternatives
|
||||
What other designs have been considered? What is the impact of not doing this?
|
||||
|
||||
### Alternative 1
|
||||
- Description
|
||||
- Trade-offs
|
||||
|
||||
### Alternative 2
|
||||
- Description
|
||||
- Trade-offs
|
||||
|
||||
## Adoption Strategy
|
||||
How will existing users adopt this? Is this a breaking change?
|
||||
|
||||
### Migration Guide
|
||||
```bash
|
||||
# Steps to migrate
|
||||
```
|
||||
|
||||
### Deprecation Timeline
|
||||
- **Announcement**:
|
||||
- **Deprecation**:
|
||||
- **Removal**:
|
||||
|
||||
## Unresolved Questions
|
||||
- Question 1
|
||||
- Question 2
|
||||
|
||||
## Future Possibilities
|
||||
What future work does this enable?
|
||||
|
||||
## Impact Assessment
|
||||
### Performance
|
||||
Expected performance impact.
|
||||
|
||||
### Security
|
||||
Security considerations and implications.
|
||||
|
||||
### Compatibility
|
||||
- **Backward Compatible**: [Yes / No]
|
||||
- **Breaking Changes**: [List]
|
||||
|
||||
### Maintenance
|
||||
Long-term maintenance considerations.
|
||||
|
||||
## Community Input
|
||||
### Stakeholders
|
||||
- [ ] Core team
|
||||
- [ ] Module developers
|
||||
- [ ] End users
|
||||
- [ ] Enterprise customers
|
||||
|
||||
### Feedback Period
|
||||
**Duration**: [e.g., 2 weeks]
|
||||
**Deadline**: [date]
|
||||
|
||||
## Implementation Timeline
|
||||
### Phase 1: Design
|
||||
- [ ] RFC discussion
|
||||
- [ ] Design finalization
|
||||
- [ ] Approval
|
||||
|
||||
### Phase 2: Implementation
|
||||
- [ ] Core implementation
|
||||
- [ ] Tests
|
||||
- [ ] Documentation
|
||||
|
||||
### Phase 3: Release
|
||||
- [ ] Beta release
|
||||
- [ ] Feedback collection
|
||||
- [ ] Stable release
|
||||
|
||||
## Success Metrics
|
||||
How will we measure success?
|
||||
- Metric 1
|
||||
- Metric 2
|
||||
|
||||
## References
|
||||
- Related RFCs:
|
||||
- External documentation:
|
||||
- Prior art:
|
||||
|
||||
## Open Questions for Community
|
||||
1. Question 1?
|
||||
2. Question 2?
|
||||
|
||||
---
|
||||
**Note**: This RFC is open for community discussion. Please provide feedback in the comments below.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: Security Vulnerability Report
|
||||
about: Report a security vulnerability (use only for non-critical issues)
|
||||
title: '[SECURITY] '
|
||||
labels: 'security'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ⚠️ IMPORTANT: Private Disclosure Required
|
||||
|
||||
**For critical security vulnerabilities, DO NOT use this template.**
|
||||
Follow the process in [SECURITY.md](../SECURITY.md) for responsible disclosure.
|
||||
|
||||
Use this template only for:
|
||||
- Security improvements
|
||||
- Non-critical security suggestions
|
||||
- Security documentation updates
|
||||
|
||||
---
|
||||
|
||||
## Security Issue
|
||||
|
||||
**Severity**:
|
||||
<!-- Low, Medium, or informational only -->
|
||||
|
||||
## Description
|
||||
<!-- Describe the security concern or improvement suggestion -->
|
||||
|
||||
## Affected Components
|
||||
<!-- List the affected files, features, or components -->
|
||||
|
||||
## Suggested Mitigation
|
||||
<!-- Describe how this could be addressed -->
|
||||
|
||||
## Standards Reference
|
||||
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||
- [ ] SPDX license identifiers
|
||||
- [ ] Secret management
|
||||
- [ ] Dependency security
|
||||
- [ ] Access control
|
||||
- [ ] Other: [specify]
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context about the security concern -->
|
||||
|
||||
## Checklist
|
||||
- [ ] This is NOT a critical vulnerability requiring private disclosure
|
||||
- [ ] I have reviewed the SECURITY.md policy
|
||||
- [ ] I have provided sufficient detail for evaluation
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
name: ".mokogitea Test Template"
|
||||
about: "Verify .mokogitea issue templates work"
|
||||
labels: ["test"]
|
||||
---
|
||||
|
||||
This template was loaded from `.mokogitea/ISSUE_TEMPLATE/`.
|
||||
|
||||
If you can see this, the `.mokogitea` dot-folder feature is working.
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: Version Bump
|
||||
about: Request or track a version change
|
||||
title: '[VERSION] '
|
||||
labels: 'version, type: version'
|
||||
assignees: 'jmiller'
|
||||
---
|
||||
|
||||
## Version Change
|
||||
|
||||
**Current version**: <!-- e.g., 01.02.03 -->
|
||||
**Requested version**: <!-- e.g., 01.03.00 -->
|
||||
**Change type**: <!-- patch / minor / major -->
|
||||
|
||||
## Reason
|
||||
|
||||
<!-- Why is this version bump needed? -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] README.md `VERSION:` field updated
|
||||
- [ ] CHANGELOG.md entry added
|
||||
- [ ] Module descriptor version updated (Dolibarr: `$this->version`, Joomla: `<version>`)
|
||||
- [ ] All file headers will be auto-propagated by `sync-version-on-merge` workflow
|
||||
@@ -1,134 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# BRIEF: Build MokoGitea Docker image, push to registry, and deploy
|
||||
|
||||
name: Deploy MokoGitea
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version tag (e.g. v1.26.1-moko.04.00.00)'
|
||||
required: true
|
||||
default: 'latest'
|
||||
environment:
|
||||
description: 'Target environment'
|
||||
required: true
|
||||
default: 'dev'
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- production
|
||||
|
||||
concurrency:
|
||||
group: deploy-mokogitea
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
REGISTRY: git.mokoconsulting.tech
|
||||
IMAGE: mokoconsulting/mokogitea
|
||||
DEPLOY_HOST: git.mokoconsulting.tech
|
||||
DEPLOY_PORT: 2918
|
||||
DEPLOY_USER: mokoconsulting
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine settings
|
||||
id: config
|
||||
run: |
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
ENV="${{ github.event.inputs.environment }}"
|
||||
|
||||
if [ "$ENV" = "production" ]; then
|
||||
echo "compose_dir=/opt/gitea" >> $GITHUB_OUTPUT
|
||||
echo "container=mokogitea" >> $GITHUB_OUTPUT
|
||||
echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT
|
||||
echo "branch=main" >> $GITHUB_OUTPUT
|
||||
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT
|
||||
echo "container=mokogitea-dev" >> $GITHUB_OUTPUT
|
||||
echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT
|
||||
echo "branch=dev" >> $GITHUB_OUTPUT
|
||||
echo "tag=${VERSION}-dev" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build, push, and deploy via SSH
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
TAG: ${{ steps.config.outputs.tag }}
|
||||
BRANCH: ${{ steps.config.outputs.branch }}
|
||||
SOURCE_DIR: ${{ steps.config.outputs.source_dir }}
|
||||
COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }}
|
||||
CONTAINER: ${{ steps.config.outputs.container }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
|
||||
SSH_CMD="ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}"
|
||||
|
||||
$SSH_CMD "echo 'SSH connected'"
|
||||
|
||||
# Pull latest source
|
||||
$SSH_CMD "
|
||||
set -e
|
||||
if [ ! -d ${SOURCE_DIR}/.git ]; then
|
||||
git clone -b ${BRANCH} https://git.mokoconsulting.tech/MokoConsulting/MokoGitea.git ${SOURCE_DIR}
|
||||
fi
|
||||
cd ${SOURCE_DIR}
|
||||
git fetch origin ${BRANCH}
|
||||
git reset --hard origin/${BRANCH}
|
||||
"
|
||||
|
||||
# Build Docker image
|
||||
$SSH_CMD "
|
||||
set -e
|
||||
cd ${SOURCE_DIR}
|
||||
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
||||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG} \
|
||||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \
|
||||
-f Dockerfile .
|
||||
"
|
||||
|
||||
# Push to container registry
|
||||
$SSH_CMD "
|
||||
set -e
|
||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG}
|
||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
|
||||
"
|
||||
|
||||
# Update compose and restart
|
||||
$SSH_CMD "
|
||||
set -e
|
||||
cd ${COMPOSE_DIR}
|
||||
sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:${TAG}|' docker-compose.yml
|
||||
docker compose up -d ${CONTAINER}
|
||||
"
|
||||
|
||||
# Health check
|
||||
$SSH_CMD "
|
||||
for i in 1 2 3 4 5 6 7 8; do
|
||||
sleep 15
|
||||
if docker inspect --format='{{.State.Health.Status}}' ${CONTAINER} 2>/dev/null | grep -q healthy; then
|
||||
echo 'Container healthy!'
|
||||
docker inspect --format='Image: {{.Config.Image}}' ${CONTAINER}
|
||||
exit 0
|
||||
fi
|
||||
echo \"Waiting... (attempt \$i/8)\"
|
||||
done
|
||||
echo 'Health check failed'
|
||||
docker logs ${CONTAINER} --tail 20
|
||||
exit 1
|
||||
"
|
||||
|
||||
- name: Verify
|
||||
run: |
|
||||
sleep 5
|
||||
curl -sf https://${{ env.DEPLOY_HOST }}/api/healthz && echo " — API healthy"
|
||||
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
run: echo "::error::Deploy failed for ${{ steps.config.outputs.tag }}"
|
||||
@@ -1,90 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Enforces branch merge policy:
|
||||
# feature/* → dev only
|
||||
# fix/* → dev only
|
||||
# hotfix/* → dev or main (emergency)
|
||||
# dev → main only
|
||||
# alpha/* → dev only
|
||||
# beta/* → dev only
|
||||
# rc/* → main only
|
||||
|
||||
name: Branch Policy Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
jobs:
|
||||
check-target:
|
||||
name: Verify merge target
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check branch policy
|
||||
run: |
|
||||
HEAD="${{ github.head_ref }}"
|
||||
BASE="${{ github.base_ref }}"
|
||||
|
||||
echo "PR: ${HEAD} → ${BASE}"
|
||||
|
||||
ALLOWED=true
|
||||
REASON=""
|
||||
|
||||
case "$HEAD" in
|
||||
feature/*|feat/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Feature branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
fix/*|bugfix/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Fix branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
hotfix/*)
|
||||
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
alpha/*|beta/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Pre-release branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
rc/*)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Release candidate branches must target 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
dev)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$ALLOWED" = false ]; then
|
||||
echo "::error::${REASON}"
|
||||
echo ""
|
||||
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,170 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# BRIEF: Auto-build RC release on PR to main, update RC update stream
|
||||
|
||||
name: "PR RC Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
REGISTRY: git.mokoconsulting.tech
|
||||
IMAGE: mokoconsulting/mokogitea
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
rc-release:
|
||||
name: Build RC Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check target branch
|
||||
id: guard
|
||||
env:
|
||||
BASE_BRANCH: ${{ github.base_ref }}
|
||||
run: |
|
||||
echo "PR target: ${BASE_BRANCH}"
|
||||
if [ "$BASE_BRANCH" != "main" ]; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Skipping RC — only for PRs targeting main"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout PR branch
|
||||
if: steps.guard.outputs.skip != 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine RC version
|
||||
if: steps.guard.outputs.skip != 'true'
|
||||
id: version
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
BASE_VERSION=$(sed -n 's/.*<version>\(.*\)<\/version>.*/\1/p' updates.xml | head -1)
|
||||
[ -z "$BASE_VERSION" ] && BASE_VERSION="04.00.00"
|
||||
RC_VERSION="${BASE_VERSION}-rc.${PR_NUMBER}"
|
||||
RC_TAG="v1.26.1-moko.${RC_VERSION}"
|
||||
echo "version=$RC_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$RC_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "RC version: $RC_VERSION (tag: $RC_TAG)"
|
||||
|
||||
- name: Update updates.xml RC channel
|
||||
if: steps.guard.outputs.skip != 'true'
|
||||
env:
|
||||
RC_VERSION: ${{ steps.version.outputs.version }}
|
||||
RC_TAG: ${{ steps.version.outputs.tag }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
DOCKER_TAG="${REGISTRY}/${IMAGE}:${RC_TAG}"
|
||||
|
||||
python3 << 'PYEOF'
|
||||
import os, re
|
||||
|
||||
rc_version = os.environ["RC_VERSION"]
|
||||
rc_tag = os.environ["RC_TAG"]
|
||||
pr_url = os.environ["PR_URL"]
|
||||
pr_num = os.environ["PR_NUM"]
|
||||
docker_tag = os.environ["REGISTRY"] + "/" + os.environ["IMAGE"] + ":" + rc_tag
|
||||
|
||||
entry = f""" <update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea RC from PR #{pr_num}</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<version>{rc_version}</version>
|
||||
<client>server</client>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<infourl title="MokoGitea RC">{pr_url}</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="docker">{docker_tag}</downloadurl>
|
||||
</downloads>
|
||||
<sha256></sha256>
|
||||
<targetplatform name="mokogitea" version="((1\\.25\\.)|(1\\.26\\.))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>"""
|
||||
|
||||
content = open("updates.xml").read()
|
||||
# Remove existing RC entry
|
||||
content = re.sub(
|
||||
r"\s*<update>[\s\S]*?<tag>rc</tag>[\s\S]*?</update>",
|
||||
"",
|
||||
content,
|
||||
)
|
||||
# Insert before </updates>
|
||||
content = content.replace("</updates>", entry + "\n</updates>")
|
||||
open("updates.xml", "w").write(content)
|
||||
print(f"Updated updates.xml with RC entry: {rc_version}")
|
||||
PYEOF
|
||||
|
||||
- name: Create RC release
|
||||
if: steps.guard.outputs.skip != 'true'
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
RC_TAG: ${{ steps.version.outputs.tag }}
|
||||
RC_VERSION: ${{ steps.version.outputs.version }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
API_BASE: https://${{ env.REGISTRY }}/api/v1/repos/${{ github.repository }}
|
||||
run: |
|
||||
# Delete existing RC release/tag if present
|
||||
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API_BASE}/releases/tags/${RC_TAG}" 2>/dev/null || true
|
||||
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API_BASE}/tags/${RC_TAG}" 2>/dev/null || true
|
||||
|
||||
# Create prerelease
|
||||
python3 << PYEOF
|
||||
import json, os, urllib.request
|
||||
|
||||
api = os.environ["API_BASE"]
|
||||
token = os.environ["GITEA_TOKEN"]
|
||||
payload = json.dumps({
|
||||
"tag_name": os.environ["RC_TAG"],
|
||||
"target_commitish": os.environ["HEAD_SHA"],
|
||||
"name": f"RC: {os.environ['PR_TITLE']}",
|
||||
"body": f"Release candidate from PR #{os.environ['PR_NUMBER']}\n\nPR: {os.environ['PR_URL']}\nDocker: docker pull {os.environ['REGISTRY']}/{os.environ['IMAGE']}:{os.environ['RC_TAG']}",
|
||||
"draft": False,
|
||||
"prerelease": True,
|
||||
}).encode()
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"{api}/releases",
|
||||
data=payload,
|
||||
headers={
|
||||
"Authorization": f"token {token}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
result = json.loads(resp.read())
|
||||
print(f"Created RC release: {result.get('tag_name')}")
|
||||
PYEOF
|
||||
|
||||
- name: Commit updates.xml
|
||||
if: steps.guard.outputs.skip != 'true'
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
PR_NUM: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
git config user.name "MokoGitea Bot"
|
||||
git config user.email "deploy@mokoconsulting.tech"
|
||||
git add updates.xml
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to updates.xml"
|
||||
else
|
||||
git commit -m "chore(ci): update RC stream for PR #${PR_NUM}"
|
||||
git push origin "HEAD:${HEAD_REF}" || echo "Push failed"
|
||||
fi
|
||||
@@ -1,12 +0,0 @@
|
||||
# Test workflow to verify .mokogitea/ directory is discovered
|
||||
name: Test .mokogitea workflows
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Verify .mokogitea
|
||||
run: echo "This workflow ran from .mokogitea/workflows/ — feature works!"
|
||||
@@ -1,167 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# BRIEF: Sync upstream Gitea bug fixes into MokoGitea issue tracker
|
||||
|
||||
name: Upstream Bug Sync
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *' # daily at 08:00 UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days_back:
|
||||
description: 'How many days back to scan (default: 7)'
|
||||
required: false
|
||||
default: '7'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Sync upstream bugs
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
MOKOGITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
MOKOGITEA_URL: https://git.mokoconsulting.tech
|
||||
MOKOGITEA_REPO: MokoConsulting/MokoGitea
|
||||
UPSTREAM_BRANCH: release/v1.26
|
||||
DAYS_BACK: ${{ github.event.inputs.days_back || '7' }}
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import json, os, re, sys, urllib.parse, urllib.request
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
GH_TOKEN = os.environ["GH_TOKEN"]
|
||||
MOKO_TOKEN = os.environ["MOKOGITEA_TOKEN"]
|
||||
MOKO_URL = os.environ["MOKOGITEA_URL"]
|
||||
MOKO_REPO = os.environ["MOKOGITEA_REPO"]
|
||||
BRANCH = os.environ["UPSTREAM_BRANCH"]
|
||||
DAYS = int(os.environ.get("DAYS_BACK", "7"))
|
||||
|
||||
# Label IDs in MokoGitea
|
||||
LABELS = {
|
||||
"type_bug": 5757, "upstream": 5758, "security": 5032,
|
||||
"critical": 5018, "high": 5019, "medium": 5020, "low": 5021,
|
||||
}
|
||||
|
||||
def gh_get(url):
|
||||
req = urllib.request.Request(url, headers={
|
||||
"Authorization": f"token {GH_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
})
|
||||
with urllib.request.urlopen(req) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
def moko_get(path):
|
||||
req = urllib.request.Request(f"{MOKO_URL}/api/v1/{path}", headers={
|
||||
"Authorization": f"token {MOKO_TOKEN}",
|
||||
})
|
||||
with urllib.request.urlopen(req) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
def moko_post(path, data):
|
||||
payload = json.dumps(data).encode()
|
||||
req = urllib.request.Request(f"{MOKO_URL}/api/v1/{path}",
|
||||
data=payload, method="POST", headers={
|
||||
"Authorization": f"token {MOKO_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
with urllib.request.urlopen(req) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
# ── Step 1: Find recently merged upstream PRs ──
|
||||
since = (datetime.now(timezone.utc) - timedelta(days=DAYS)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
query = f"repo:go-gitea/gitea is:pr is:merged base:{BRANCH} merged:>={since}"
|
||||
encoded = urllib.parse.quote(query)
|
||||
print(f"Scanning: {query}")
|
||||
|
||||
result = gh_get(f"https://api.github.com/search/issues?q={encoded}&per_page=100&sort=updated&order=desc")
|
||||
total = result["total_count"]
|
||||
print(f"Found {total} merged PRs in the last {DAYS} days")
|
||||
|
||||
if total == 0:
|
||||
print("Nothing to sync.")
|
||||
sys.exit(0)
|
||||
|
||||
# ── Step 2: Filter for bug/security fixes ──
|
||||
bugs = []
|
||||
for pr in result["items"]:
|
||||
title = pr["title"]
|
||||
label_names = [l["name"].lower() for l in pr.get("labels", [])]
|
||||
is_fix = title.lower().startswith("fix")
|
||||
is_security = any("security" in l for l in label_names) or "[security]" in title.lower()
|
||||
is_bug = any("bug" in l for l in label_names)
|
||||
|
||||
if not (is_fix or is_security or is_bug):
|
||||
continue
|
||||
|
||||
refs = re.findall(r"#(\d+)", title)
|
||||
severity = "critical" if is_security and "[security]" in title.lower() else \
|
||||
"high" if is_security else "medium"
|
||||
|
||||
bugs.append({
|
||||
"number": pr["number"], "title": title, "url": pr["html_url"],
|
||||
"severity": severity, "is_security": is_security, "refs": refs,
|
||||
"merged": pr.get("pull_request", {}).get("merged_at", "")[:10],
|
||||
})
|
||||
|
||||
print(f"Filtered to {len(bugs)} bug/security fixes")
|
||||
if not bugs:
|
||||
sys.exit(0)
|
||||
|
||||
# ── Step 3: Collect already-tracked PR numbers ──
|
||||
tracked = set()
|
||||
for state in ["open", "closed"]:
|
||||
try:
|
||||
issues = moko_get(f"repos/{MOKO_REPO}/issues?state={state}&type=issues&limit=50&labels=upstream")
|
||||
for iss in issues:
|
||||
text = (iss.get("body") or "") + " " + (iss.get("title") or "")
|
||||
tracked.update(re.findall(r"(?:#|/pull/)(\d{4,})", text))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"Already tracked: {len(tracked)} upstream PRs")
|
||||
|
||||
# ── Step 4: Create issues for new bugs ──
|
||||
created = skipped = errors = 0
|
||||
for bug in bugs:
|
||||
if any(r in tracked for r in bug["refs"]):
|
||||
print(f" SKIP #{bug['number']}: {bug['title'][:55]} (tracked)")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
labels = [LABELS["type_bug"], LABELS["upstream"], LABELS[bug["severity"]]]
|
||||
if bug["is_security"]:
|
||||
labels.append(LABELS["security"])
|
||||
|
||||
body = (
|
||||
f"## Summary\n\n"
|
||||
f"Upstream bug fix merged into `{BRANCH}`.\n\n"
|
||||
f"## Upstream Reference\n\n"
|
||||
f"- PR: {bug['url']}\n"
|
||||
f"- Merged: {bug['merged']}\n"
|
||||
f"- Branch: {BRANCH}\n\n"
|
||||
f"## Severity: {bug['severity'].title()}"
|
||||
f"{' (security)' if bug['is_security'] else ''}\n\n"
|
||||
f"## Action\n\n"
|
||||
f"Cherry-pick from upstream `{BRANCH}` branch.\n\n"
|
||||
f"---\n"
|
||||
f"*Auto-created by upstream-bug-sync workflow*\n"
|
||||
f"*Authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>*"
|
||||
)
|
||||
|
||||
try:
|
||||
iss = moko_post(f"repos/{MOKO_REPO}/issues", {
|
||||
"title": bug["title"], "body": body, "labels": labels,
|
||||
})
|
||||
print(f" CREATED #{iss['number']}: {bug['title'][:55]}")
|
||||
created += 1
|
||||
except Exception as e:
|
||||
print(f" ERROR #{bug['number']}: {e}")
|
||||
errors += 1
|
||||
|
||||
print(f"\n=== Done: {created} created, {skipped} skipped, {errors} errors ===")
|
||||
PYEOF
|
||||
@@ -1,7 +1,6 @@
|
||||
audit=false
|
||||
fund=false
|
||||
update-notifier=false
|
||||
package-lock=true
|
||||
save-exact=true
|
||||
auto-install-peers=true
|
||||
dedupe-peer-dependents=false
|
||||
enable-pre-post-scripts=true
|
||||
lockfile-version=3
|
||||
|
||||
+3
-1
@@ -21,7 +21,9 @@ rules:
|
||||
comments-indentation:
|
||||
level: error
|
||||
|
||||
document-start: disable
|
||||
document-start:
|
||||
level: error
|
||||
present: false
|
||||
|
||||
document-end:
|
||||
present: false
|
||||
|
||||
@@ -1,16 +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
|
||||
- Run single go tests with `go test -run '^TestName$' ./modulepath/`
|
||||
- Run single js test files with `pnpm exec vitest <path-filter>`
|
||||
- Run single playwright e2e test files with `GITEA_TEST_E2E_FLAGS='<filepath>' make test-e2e`
|
||||
- Add the current year into the copyright header of new `.go` files
|
||||
- Ensure no trailing whitespace in edited files
|
||||
- Use Conventional Commits format for commit messages and PR titles (e.g. `type(scope): subject`)
|
||||
- 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
|
||||
- For CSS layout, prefer `flex-*` helpers over per-child `tw-ml-*` / `tw-mr-*` margins; fall back to `tw-*` utilities when specificity requires `!important`
|
||||
- Include authorship attribution in issue and pull request comments
|
||||
- Add `Co-Authored-By` lines to all commits, indicating name and model used
|
||||
+9
-958
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -30,7 +30,7 @@ These are the values to which people in the Gitea community should aspire.
|
||||
- **Be constructive.**
|
||||
- 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 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 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.**
|
||||
@@ -42,7 +42,7 @@ People are complicated. You should expect to be misunderstood and to misundersta
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
+387
-74
@@ -1,25 +1,19 @@
|
||||
# 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>
|
||||
|
||||
- [Contribution Guidelines](#contribution-guidelines)
|
||||
- [Introduction](#introduction)
|
||||
- [AI Contribution Policy](#ai-contribution-policy)
|
||||
- [Issues](#issues)
|
||||
- [How to report issues](#how-to-report-issues)
|
||||
- [Types of issues](#types-of-issues)
|
||||
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
|
||||
- [Issue locking](#issue-locking)
|
||||
- [Building Gitea](#building-gitea)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Backend](#backend)
|
||||
- [Frontend](#frontend)
|
||||
- [Design guideline](#design-guideline)
|
||||
- [Styleguide](#styleguide)
|
||||
- [Copyright](#copyright)
|
||||
- [Testing](#testing)
|
||||
@@ -27,39 +21,52 @@ This document explains how to contribute changes to the Gitea project. Topic-spe
|
||||
- [Code review](#code-review)
|
||||
- [Pull request format](#pull-request-format)
|
||||
- [PR title and summary](#pr-title-and-summary)
|
||||
- [Milestone](#milestone)
|
||||
- [Labels](#labels)
|
||||
- [Breaking PRs](#breaking-prs)
|
||||
- [What is a breaking PR?](#what-is-a-breaking-pr)
|
||||
- [How to handle breaking PRs?](#how-to-handle-breaking-prs)
|
||||
- [Maintaining open PRs](#maintaining-open-prs)
|
||||
- [Reviewing PRs](#reviewing-prs)
|
||||
- [For PR authors](#for-pr-authors)
|
||||
- [Getting PRs merged](#getting-prs-merged)
|
||||
- [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)
|
||||
- [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)
|
||||
- [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>
|
||||
|
||||
## 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). \
|
||||
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/).
|
||||
|
||||
## 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
|
||||
|
||||
### 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. \
|
||||
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
|
||||
|
||||
@@ -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).
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
| | |
|
||||
| :-------------------- | :--------------------------------------------------------------------------- |
|
||||
| | |
|
||||
| :-------------------- | :---------------------------------------------------------------- |
|
||||
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|
||||
|``make lint-frontend`` | lint frontend files |
|
||||
|``make lint-backend`` | lint backend files |
|
||||
|``make lint-frontend`` | lint frontend files |
|
||||
|``make lint-backend`` | lint backend files |
|
||||
|
||||
- run tests (we suggest running them on Linux)
|
||||
|
||||
| Command | Action | |
|
||||
|:----------------------------------------------|:-----------------------------------------------------| ------------------------------------------- |
|
||||
| ``make test-backend[\#SpecificTestName]`` | run unit test(s) | |
|
||||
| ``make test-integration[\#SpecificTestName]`` | run [integration](tests/integration) test(s) | [More details](tests/integration/README.md) |
|
||||
| ``make test-e2e`` | run [end-to-end](tests/e2e) test(s) using Playwright | |
|
||||
|
||||
- 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: 4 on CI, 1 locally) |
|
||||
| Command | Action | |
|
||||
| :------------------------------------- | :----------------------------------------------- | ------------ |
|
||||
|``make test[\#SpecificTestName]`` | run unit test(s) | |
|
||||
|``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) |
|
||||
|``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/README.md) |
|
||||
|
||||
## Translation
|
||||
|
||||
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. \
|
||||
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.
|
||||
@@ -165,8 +192,6 @@ The tool `go run build/backport-locale.go` can be used to backport locales from
|
||||
|
||||
## Code review
|
||||
|
||||
How labels, milestones, and the merge queue work is documented in [docs/community-governance.md](docs/community-governance.md).
|
||||
|
||||
### Pull request format
|
||||
|
||||
Please try to make your pull request easy to review for us. \
|
||||
@@ -189,22 +214,6 @@ In the PR title, describe the problem you are fixing, not how you are fixing it.
|
||||
Use the first comment as a summary of your PR. \
|
||||
In the PR summary, you can describe exactly how you are fixing this problem.
|
||||
|
||||
PR titles must follow the [Conventional Commits](https://www.conventionalcommits.org/) format, because PRs are squash-merged and the PR title becomes the resulting commit message:
|
||||
|
||||
```text
|
||||
type(scope)!: subject
|
||||
```
|
||||
|
||||
The allowed types are `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, and `test`. The generic `chore` type is intentionally not accepted; pick a more descriptive type instead.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
fix(web): prevent avatar upload crash on empty file
|
||||
feat(api): add pagination to repo hooks list
|
||||
ci(workflows): lint PR titles with commitlint
|
||||
```
|
||||
|
||||
Keep this summary up-to-date as the PR evolves. \
|
||||
If your PR changes the UI, you must add **after** screenshots in the PR summary. \
|
||||
If you are not implementing a new feature, you should also post **before** screenshots for comparison.
|
||||
@@ -227,6 +236,29 @@ Fixes/Closes/Resolves #<ISSUE_NR_Y>.
|
||||
to your summary. \
|
||||
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
|
||||
|
||||
#### What is a breaking PR?
|
||||
@@ -255,29 +287,165 @@ Breaking PRs will not be merged as long as not both of these requirements are me
|
||||
|
||||
### 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.
|
||||
- **Discussion**: A discussion is always welcome and should be used to clarify the changes and the intent of the PR.
|
||||
- **Help**: If you need help with the PR or comments are unclear, ask for clarification.
|
||||
- create a backport PR if needed once the initial PR was merged
|
||||
- remove the PR from the merge queue after the PR merged
|
||||
- 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
|
||||
|
||||
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.**
|
||||
|
||||
## 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)
|
||||
|
||||
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). \
|
||||
@@ -291,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`.
|
||||
|
||||
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`
|
||||
|
||||
+32
-33
@@ -1,51 +1,48 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
|
||||
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 stage
|
||||
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
|
||||
|
||||
# Build backend for each target platform
|
||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-direct}
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS=""
|
||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||
ENV TAGS="bindata timetzdata $TAGS"
|
||||
ARG CGO_EXTRA_CFLAGS
|
||||
|
||||
# Build deps
|
||||
RUN apk --no-cache add \
|
||||
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
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
# Use COPY instead of bind mount as read-only one breaks makefile state tracking
|
||||
COPY --exclude=.git/ . .
|
||||
COPY --from=frontend-build /src/public/assets public/assets
|
||||
|
||||
# Build gitea, .git mount is required for version data
|
||||
# GOFLAGS=-p 1 serializes compilation to prevent OOM on low-memory servers
|
||||
ARG GOFLAGS="-p 1"
|
||||
RUN --mount=type=cache,target="/root/.cache/go-build" \
|
||||
--mount=type=bind,source=".git/",target=".git/" \
|
||||
GOFLAGS="${GOFLAGS}" make backend
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||
&& make clean-all build
|
||||
|
||||
# Begin env-to-ini build
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
# Copy local files
|
||||
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 \
|
||||
/tmp/local/usr/local/bin/* \
|
||||
/tmp/local/usr/local/bin/gitea \
|
||||
/tmp/local/etc/s6/gitea/* \
|
||||
/tmp/local/etc/s6/openssh/* \
|
||||
/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
|
||||
|
||||
@@ -60,7 +57,8 @@ RUN apk --no-cache add \
|
||||
s6 \
|
||||
sqlite \
|
||||
su-exec \
|
||||
gnupg
|
||||
gnupg \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN addgroup \
|
||||
-S -g 1000 \
|
||||
@@ -74,14 +72,15 @@ RUN addgroup \
|
||||
git && \
|
||||
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 GITEA_CUSTOM=/data/gitea
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
# HINT: HEALTH-CHECK-ENDPOINT: don't use HEALTHCHECK, search this hint keyword for more information
|
||||
ENTRYPOINT ["/usr/bin/entrypoint"]
|
||||
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
|
||||
|
||||
+31
-29
@@ -1,45 +1,46 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
|
||||
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 stage
|
||||
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
|
||||
|
||||
# Build backend for each target platform
|
||||
FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-direct}
|
||||
|
||||
ARG GITEA_VERSION
|
||||
ARG TAGS=""
|
||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||
ENV TAGS="bindata timetzdata $TAGS"
|
||||
ARG CGO_EXTRA_CFLAGS
|
||||
|
||||
# Build deps
|
||||
#Build deps
|
||||
RUN apk --no-cache add \
|
||||
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
|
||||
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
|
||||
RUN --mount=type=cache,target="/root/.cache/go-build" \
|
||||
--mount=type=bind,source=".git/",target=".git/" \
|
||||
make backend
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
|
||||
&& make clean-all build
|
||||
|
||||
# Begin env-to-ini build
|
||||
RUN go build contrib/environment-to-ini/environment-to-ini.go
|
||||
|
||||
# Copy local files
|
||||
COPY docker/rootless /tmp/local
|
||||
|
||||
# Set permissions for builds that made under windows which strips the executable bit from file
|
||||
RUN chmod 755 /tmp/local/usr/local/bin/* \
|
||||
/go/src/code.gitea.io/gitea/gitea
|
||||
# Set permissions
|
||||
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||
/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
|
||||
|
||||
@@ -51,7 +52,7 @@ RUN apk --no-cache add \
|
||||
git \
|
||||
curl \
|
||||
gnupg \
|
||||
openssh-keygen
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN addgroup \
|
||||
-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 --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
|
||||
USER 1000:1000
|
||||
@@ -83,6 +86,5 @@ ENV HOME="/var/lib/gitea/git"
|
||||
VOLUME ["/var/lib/gitea", "/etc/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"]
|
||||
CMD []
|
||||
|
||||
+2
-3
@@ -36,7 +36,9 @@ a1012112796 <1012112796@qq.com> (@a1012112796)
|
||||
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
|
||||
Norwin Roosen <git@nroo.de> (@noerw)
|
||||
Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu)
|
||||
Patrick Schratz <patrick.schratz@gmail.com> (@pat-s)
|
||||
Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R)
|
||||
Steven Kriegler <sk.bunsenbrenner@gmail.com> (@justusbunsi)
|
||||
Jimmy Praet <jimmy.praet@telenet.be> (@jpraet)
|
||||
Leon Hofmeister <dev.lh@web.de> (@delvh)
|
||||
Wim <wim@42.be> (@42wim)
|
||||
@@ -62,6 +64,3 @@ Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
|
||||
hiifong <i@hiif.ong> (@hiifong)
|
||||
metiftikci <metiftikci@hotmail.com> (@metiftikci)
|
||||
Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
|
||||
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)
|
||||
TheFox <thefox0x7@gmail.com> (@TheFox0x7)
|
||||
Nicolas <bircni@icloud.com> (@bircni)
|
||||
@@ -1,56 +1,52 @@
|
||||
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_DIRS := $(DIST)/binaries $(DIST)/release
|
||||
|
||||
# By default use go's 1.25 experimental json v2 library when building
|
||||
# TODO: remove when no longer experimental
|
||||
export GOEXPERIMENT ?= jsonv2
|
||||
IMPORT := code.gitea.io/gitea
|
||||
|
||||
GO ?= go
|
||||
SHASUM ?= shasum -a 256
|
||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||
COMMA := ,
|
||||
|
||||
XGO_VERSION := go-1.26.x
|
||||
XGO_VERSION := go-1.24.x
|
||||
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11 # renovate: datasource=go
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1
|
||||
|
||||
DOCKER_IMAGE ?= gitea/gitea
|
||||
DOCKER_TAG ?= latest
|
||||
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
||||
|
||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||
ifeq ($(HAS_GO), yes)
|
||||
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||
endif
|
||||
|
||||
MAKE_EVIDENCE_DIR := .make_evidence
|
||||
|
||||
# Use sqlite as default database if running tests, only do so for local tests, not in CI.
|
||||
# CI should explicitly set the database to avoid unexpected results.
|
||||
ifneq ($(findstring test-,$(MAKECMDGOALS)),)
|
||||
ifeq ($(CI),)
|
||||
GITEA_TEST_DATABASE ?= sqlite
|
||||
endif
|
||||
endif
|
||||
|
||||
TAGS ?=
|
||||
TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
|
||||
|
||||
CGO_ENABLED ?= 0
|
||||
ifneq (,$(findstring sqlite_mattn,$(TAGS))$(findstring pam,$(TAGS)))
|
||||
CGO_ENABLED = 1
|
||||
endif
|
||||
|
||||
STATIC ?=
|
||||
EXTLDFLAGS ?=
|
||||
ifneq ($(STATIC),)
|
||||
EXTLDFLAGS = -extldflags "-static"
|
||||
endif
|
||||
|
||||
ifeq ($(GOOS),windows)
|
||||
IS_WINDOWS := yes
|
||||
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
||||
@@ -58,8 +54,6 @@ else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
||||
IS_WINDOWS := yes
|
||||
endif
|
||||
endif
|
||||
|
||||
# GOFLAGS and EXTRA_GOFLAGS are for the 'go build' command only
|
||||
ifeq ($(IS_WINDOWS),yes)
|
||||
GOFLAGS := -v -buildmode=exe
|
||||
EXECUTABLE ?= gitea.exe
|
||||
@@ -67,7 +61,6 @@ else
|
||||
GOFLAGS := -v
|
||||
EXECUTABLE ?= gitea
|
||||
endif
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
||||
SED_INPLACE := sed -i
|
||||
@@ -75,10 +68,19 @@ else
|
||||
SED_INPLACE := sed -i ''
|
||||
endif
|
||||
|
||||
# GOTEST_FLAGS is for unit test and integration test
|
||||
GOTEST_FLAGS ?= -timeout 40m
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1)
|
||||
MAKE_EVIDENCE_DIR := .make_evidence
|
||||
|
||||
GOTESTFLAGS ?=
|
||||
ifeq ($(RACE_ENABLED),true)
|
||||
GOFLAGS += -race
|
||||
GOTESTFLAGS += -race
|
||||
endif
|
||||
|
||||
STORED_VERSION_FILE := VERSION
|
||||
HUGO_VERSION ?= 0.111.3
|
||||
|
||||
GITHUB_REF_TYPE ?= branch
|
||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
@@ -106,20 +108,20 @@ ifeq ($(VERSION),main)
|
||||
VERSION := main-nightly
|
||||
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
|
||||
|
||||
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/...)
|
||||
|
||||
FRONTEND_SOURCES := $(shell find web_src/js web_src/css -type f)
|
||||
FRONTEND_CONFIGS := vite.config.ts tailwind.config.ts
|
||||
FRONTEND_DEST := public/assets/.vite/manifest.json
|
||||
FRONTEND_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/.vite
|
||||
FRONTEND_DEV_LOG_LEVEL ?= warn
|
||||
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
|
||||
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
|
||||
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
|
||||
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
|
||||
|
||||
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
|
||||
|
||||
@@ -127,28 +129,43 @@ SVG_DEST_DIR := public/assets/img/svg
|
||||
|
||||
AIR_TMP_DIR := .air
|
||||
|
||||
GO_LICENSE_TMP_DIR := .go-licenses
|
||||
GO_LICENSE_FILE := assets/go-licenses.json
|
||||
|
||||
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
|
||||
TAGS ?=
|
||||
TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS))
|
||||
TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
|
||||
|
||||
GO_DIRS := build cmd models modules routers services tests tools
|
||||
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) $(GO_LICENSE_TMP_DIR)
|
||||
|
||||
GO_DIRS := build cmd models modules routers services tests
|
||||
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
|
||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.json .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml))
|
||||
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.json
|
||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
|
||||
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
|
||||
|
||||
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_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_INPUT := templates/swagger/v1_input.json
|
||||
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
||||
OPENAPI3_SPEC := templates/swagger/v1_openapi3_json.tmpl
|
||||
|
||||
TEST_MYSQL_HOST ?= mysql:3306
|
||||
TEST_MYSQL_DBNAME ?= testgitea
|
||||
@@ -161,42 +178,67 @@ TEST_PGSQL_PASSWORD ?= postgres
|
||||
TEST_PGSQL_SCHEMA ?= gtestschema
|
||||
TEST_MINIO_ENDPOINT ?= minio:9000
|
||||
TEST_MSSQL_HOST ?= mssql:1433
|
||||
TEST_MSSQL_DBNAME ?= testgitea
|
||||
TEST_MSSQL_DBNAME ?= gitea
|
||||
TEST_MSSQL_USERNAME ?= sa
|
||||
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
|
||||
|
||||
# Include local Makefile
|
||||
# Makefile.local is listed in .gitignore
|
||||
ifneq ("$(wildcard Makefile.local)","")
|
||||
include Makefile.local
|
||||
endif
|
||||
|
||||
$(foreach v, $(filter TEST_%, $(.VARIABLES)), $(eval MAKEFILE_VARS+=$v=$($v)))
|
||||
$(foreach v, $(filter GITEA_TEST_%, $(.VARIABLES)), $(eval MAKEFILE_VARS+=$v=$($v)))
|
||||
export MAKEFILE_VARS
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: help
|
||||
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)
|
||||
@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-backend[#TestSpecificName]" "run unit test (sqlite only)"
|
||||
@printf " \033[36m%-46s\033[0m %s\n" "test-integration[#TestSpecificName]" "run integration test for GITEA_TEST_DATABASE (sqlite, mysql, pgsql, mssql)"
|
||||
@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-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
|
||||
git-check:
|
||||
@if git lfs >/dev/null 2>&1 ; then : ; else \
|
||||
echo "Gitea requires git with lfs support to run tests." ; \
|
||||
exit 1; \
|
||||
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
|
||||
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
|
||||
clean: ## delete backend and integration files
|
||||
rm -f $(EXECUTABLE) test-*.test tests/*.ini
|
||||
rm -rf $(DIST) $(BINDATA_DEST_WILDCARD) man tests/integration/gitea-integration-*
|
||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
|
||||
integrations*.test \
|
||||
e2e*.test \
|
||||
tests/integration/gitea-integration-* \
|
||||
tests/integration/indexers-* \
|
||||
tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \
|
||||
tests/e2e/gitea-e2e-*/ \
|
||||
tests/e2e/indexers-*/ \
|
||||
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: ## format the Go and template code
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) fmt
|
||||
fmt: ## format the Go code
|
||||
@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'))
|
||||
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
|
||||
@# whitespace before it
|
||||
@@ -224,9 +266,9 @@ TAGS_PREREQ := $(TAGS_EVIDENCE)
|
||||
endif
|
||||
|
||||
.PHONY: generate-swagger
|
||||
generate-swagger: $(SWAGGER_SPEC) $(OPENAPI3_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)'
|
||||
|
||||
.PHONY: swagger-check
|
||||
@@ -246,21 +288,6 @@ swagger-validate: ## check if the swagger spec is valid
|
||||
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
||||
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
|
||||
|
||||
.PHONY: generate-openapi3
|
||||
generate-openapi3: $(OPENAPI3_SPEC) ## generate the OpenAPI 3.0 spec from the Swagger 2.0 spec
|
||||
|
||||
$(OPENAPI3_SPEC): $(SWAGGER_SPEC) build/generate-openapi.go $(wildcard build/openapi3gen/*.go)
|
||||
$(GO) run build/generate-openapi.go
|
||||
|
||||
.PHONY: openapi3-check
|
||||
openapi3-check: generate-openapi3
|
||||
@diff=$$(git diff --color=always '$(OPENAPI3_SPEC)'); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make generate-openapi3' and commit the result:"; \
|
||||
printf "%s" "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: checks
|
||||
checks: checks-frontend checks-backend ## run various consistency checks
|
||||
|
||||
@@ -268,10 +295,10 @@ checks: checks-frontend checks-backend ## run various consistency checks
|
||||
checks-frontend: lockfile-check svg-check ## check frontend files
|
||||
|
||||
.PHONY: checks-backend
|
||||
checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
|
||||
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
|
||||
|
||||
.PHONY: lint
|
||||
lint: lint-frontend lint-backend lint-templates lint-swagger lint-spell lint-md lint-actions lint-json lint-yaml ## lint everything
|
||||
lint: lint-frontend lint-backend lint-spell ## lint everything
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues
|
||||
@@ -283,52 +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
|
||||
|
||||
.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
|
||||
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
|
||||
lint-js: node_modules ## lint js and ts files
|
||||
pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES)
|
||||
pnpm exec vue-tsc
|
||||
lint-js: node_modules ## lint js files
|
||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: lint-js-fix
|
||||
lint-js-fix: node_modules ## lint js and ts files and fix issues
|
||||
pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES) --fix
|
||||
pnpm exec vue-tsc
|
||||
lint-js-fix: node_modules ## lint js files and fix issues
|
||||
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
|
||||
npx vue-tsc
|
||||
|
||||
.PHONY: lint-css
|
||||
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
|
||||
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
|
||||
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
|
||||
lint-md: node_modules ## lint markdown files
|
||||
pnpm exec markdownlint *.md
|
||||
|
||||
.PHONY: lint-md-fix
|
||||
lint-md-fix: node_modules ## lint markdown files and fix issues
|
||||
pnpm exec markdownlint --fix *.md
|
||||
|
||||
.PHONY: lint-pr-title
|
||||
lint-pr-title: ## lint PR title against Conventional Commits (set PR_TITLE=...)
|
||||
@node ./tools/lint-pr-title.js
|
||||
npx markdownlint *.md
|
||||
|
||||
.PHONY: lint-spell
|
||||
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
|
||||
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
|
||||
lint-go: ## lint go files
|
||||
@@ -345,6 +364,17 @@ lint-go-windows:
|
||||
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||
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
|
||||
lint-editorconfig:
|
||||
@echo "Running editorconfig check..."
|
||||
@@ -356,41 +386,37 @@ lint-actions: ## lint action workflow files
|
||||
|
||||
.PHONY: lint-templates
|
||||
lint-templates: .venv node_modules ## lint template files
|
||||
@node tools/lint-templates-svg.ts
|
||||
@uv run --frozen djlint $(shell find templates -type f -iname '*.tmpl')
|
||||
@node tools/lint-templates-svg.js
|
||||
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
|
||||
|
||||
.PHONY: lint-yaml
|
||||
lint-yaml: .venv ## lint yaml files
|
||||
@uv run --frozen 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
|
||||
@poetry run yamllint -s .
|
||||
|
||||
.PHONY: watch
|
||||
watch: ## watch everything and continuously rebuild
|
||||
@bash tools/watch.sh
|
||||
|
||||
.PHONY: watch-frontend
|
||||
watch-frontend: node_modules ## start vite dev server for frontend
|
||||
NODE_ENV=development pnpm exec vite --logLevel $(FRONTEND_DEV_LOG_LEVEL)
|
||||
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
|
||||
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
||||
NODE_ENV=development npx webpack --watch --progress
|
||||
|
||||
.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
|
||||
|
||||
.PHONY: test
|
||||
test: test-frontend test-backend ## test everything
|
||||
|
||||
.PHONY: test-backend
|
||||
test-backend: ## test backend files
|
||||
@echo "Running go test with $(GOTEST_FLAGS) -tags '$(TAGS)'..."
|
||||
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' $(GO_TEST_PACKAGES)
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||
|
||||
.PHONY: test-frontend
|
||||
test-frontend: node_modules ## test frontend files
|
||||
pnpm exec vitest
|
||||
npx vitest
|
||||
|
||||
.PHONY: test-check
|
||||
test-check:
|
||||
@@ -404,21 +430,21 @@ test-check:
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: test-backend\#%
|
||||
test-backend\#%:
|
||||
@echo "Running go test with -tags '$(TAGS)'..."
|
||||
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
.PHONY: test\#%
|
||||
test\#%:
|
||||
@echo "Running go test with -tags '$(TEST_TAGS)'..."
|
||||
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
|
||||
.PHONY: 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]\+\)$$' 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
|
||||
unit-test-coverage:
|
||||
@echo "Running unit-test-coverage $(GOTEST_FLAGS) -tags '$(TAGS)'..."
|
||||
@$(GO) test $(GOTEST_FLAGS) -tags='$(TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
@echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
|
||||
.PHONY: tidy
|
||||
tidy: ## run go mod tidy
|
||||
@@ -443,53 +469,259 @@ tidy-check: tidy
|
||||
go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses
|
||||
|
||||
$(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)
|
||||
|
||||
.PHONY: test-integration
|
||||
test-integration:
|
||||
@# Use a compiled binary: testlogger forwards gitea logs to t.Log, so `go test -v`
|
||||
@# would flood output per passing test. testcache can't help these tests anyway —
|
||||
@# they mutate the work directory, so cache inputs change between runs.
|
||||
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' -c code.gitea.io/gitea/tests/integration -o ./test-integration-$(GITEA_TEST_DATABASE).test
|
||||
./test-integration-$(GITEA_TEST_DATABASE).test
|
||||
generate-ini-sqlite:
|
||||
sed -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||
tests/sqlite.ini.tmpl > tests/sqlite.ini
|
||||
|
||||
.PHONY: test-integration\#%
|
||||
test-integration\#%:
|
||||
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' -run $(subst .,/,$*) code.gitea.io/gitea/tests/integration
|
||||
.PHONY: test-sqlite
|
||||
test-sqlite: integrations.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test
|
||||
|
||||
.PHONY: test-migration
|
||||
test-migration: migrations.integration.test migrations.individual.test
|
||||
.PHONY: test-sqlite\#%
|
||||
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: migrations.integration.test
|
||||
migrations.integration.test:
|
||||
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' code.gitea.io/gitea/tests/integration/migration-test
|
||||
.PHONY: test-sqlite-migration
|
||||
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
|
||||
|
||||
.PHONY: migrations.individual.test
|
||||
migrations.individual.test:
|
||||
@# tests of multiple packages use the same database, don't run in parallel
|
||||
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
generate-ini-mysql:
|
||||
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
|
||||
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
|
||||
-e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \
|
||||
-e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \
|
||||
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||
tests/mysql.ini.tmpl > tests/mysql.ini
|
||||
|
||||
.PHONY: migrations.individual.test\#%
|
||||
migrations.individual.test\#%:
|
||||
$(GO) test $(GOTEST_FLAGS) -tags '$(TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||
.PHONY: test-mysql
|
||||
test-mysql: integrations.mysql.test generate-ini-mysql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test
|
||||
|
||||
.PHONY: test-mysql\#%
|
||||
test-mysql\#%: integrations.mysql.test generate-ini-mysql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-mysql-migration
|
||||
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test
|
||||
|
||||
generate-ini-pgsql:
|
||||
sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \
|
||||
-e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \
|
||||
-e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \
|
||||
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
|
||||
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \
|
||||
-e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \
|
||||
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||
tests/pgsql.ini.tmpl > tests/pgsql.ini
|
||||
|
||||
.PHONY: test-pgsql
|
||||
test-pgsql: integrations.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test
|
||||
|
||||
.PHONY: test-pgsql\#%
|
||||
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-pgsql-migration
|
||||
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test
|
||||
|
||||
generate-ini-mssql:
|
||||
sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \
|
||||
-e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \
|
||||
-e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \
|
||||
-e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \
|
||||
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
|
||||
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
|
||||
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
|
||||
tests/mssql.ini.tmpl > tests/mssql.ini
|
||||
|
||||
.PHONY: test-mssql
|
||||
test-mssql: integrations.mssql.test generate-ini-mssql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test
|
||||
|
||||
.PHONY: test-mssql\#%
|
||||
test-mssql\#%: integrations.mssql.test generate-ini-mssql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-mssql-migration
|
||||
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
|
||||
|
||||
.PHONY: playwright
|
||||
playwright: deps-frontend
|
||||
@# on GitHub Actions VMs, playwright's system deps are pre-installed
|
||||
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS)
|
||||
npx playwright install $(PLAYWRIGHT_FLAGS)
|
||||
|
||||
.PHONY: test-e2e%
|
||||
test-e2e%: TEST_TYPE ?= e2e
|
||||
# Clear display env variable. Otherwise, chromium tests can fail.
|
||||
DISPLAY=
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: playwright frontend backend
|
||||
@EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh $(GITEA_TEST_E2E_FLAGS)
|
||||
test-e2e: test-e2e-sqlite
|
||||
|
||||
.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
|
||||
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||
|
||||
.PHONY: bench-mysql
|
||||
bench-mysql: integrations.mysql.test generate-ini-mysql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||
|
||||
.PHONY: bench-mssql
|
||||
bench-mssql: integrations.mssql.test generate-ini-mssql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||
|
||||
.PHONY: bench-pgsql
|
||||
bench-pgsql: integrations.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
|
||||
|
||||
.PHONY: integration-test-coverage
|
||||
integration-test-coverage: integrations.cover.test generate-ini-mysql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
||||
|
||||
.PHONY: integration-test-coverage-sqlite
|
||||
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
||||
|
||||
integrations.mysql.test: git-check $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
|
||||
|
||||
integrations.pgsql.test: git-check $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test
|
||||
|
||||
integrations.mssql.test: git-check $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mssql.test
|
||||
|
||||
integrations.sqlite.test: git-check $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)'
|
||||
|
||||
integrations.cover.test: git-check $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test
|
||||
|
||||
integrations.cover.sqlite.test: git-check $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)'
|
||||
|
||||
.PHONY: migrations.mysql.test
|
||||
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test
|
||||
|
||||
.PHONY: migrations.pgsql.test
|
||||
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test
|
||||
|
||||
.PHONY: migrations.mssql.test
|
||||
migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./migrations.mssql.test
|
||||
|
||||
.PHONY: migrations.sqlite.test
|
||||
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)'
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./migrations.sqlite.test
|
||||
|
||||
.PHONY: migrations.individual.mysql.test
|
||||
migrations.individual.mysql.test: $(GO_SOURCES)
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test\#%
|
||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||
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
|
||||
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.mssql.test\#%
|
||||
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
||||
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
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||
|
||||
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
|
||||
check: test
|
||||
|
||||
.PHONY: install $(TAGS_PREREQ)
|
||||
install: $(wildcard *.go)
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
||||
|
||||
.PHONY: build
|
||||
build: frontend backend ## build everything
|
||||
|
||||
.PHONY: frontend
|
||||
frontend: $(FRONTEND_DEST) ## build frontend files
|
||||
frontend: $(WEBPACK_DEST) ## build frontend files
|
||||
|
||||
.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
|
||||
.PHONY: generate
|
||||
@@ -505,13 +737,10 @@ generate-go: $(TAGS_PREREQ)
|
||||
|
||||
.PHONY: security-check
|
||||
security-check:
|
||||
GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./... || true
|
||||
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
|
||||
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
||||
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
|
||||
$(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 $@
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: release
|
||||
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
|
||||
@@ -577,66 +806,59 @@ deps-backend: ## install backend dependencies
|
||||
deps-tools: ## install tool dependencies
|
||||
$(GO) install $(AIR_PACKAGE) & \
|
||||
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
||||
$(GO) install $(GOFUMPT_PACKAGE) & \
|
||||
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \
|
||||
$(GO) install $(GXZ_PACKAGE) & \
|
||||
$(GO) install $(MISSPELL_PACKAGE) & \
|
||||
$(GO) install $(SWAGGER_PACKAGE) & \
|
||||
$(GO) install $(XGO_PACKAGE) & \
|
||||
$(GO) install $(GO_LICENSES_PACKAGE) & \
|
||||
$(GO) install $(GOVULNCHECK_PACKAGE) & \
|
||||
$(GO) install $(ACTIONLINT_PACKAGE) & \
|
||||
$(GO) install $(GOPLS_PACKAGE) & \
|
||||
wait
|
||||
|
||||
node_modules: pnpm-lock.yaml
|
||||
pnpm install --frozen-lockfile
|
||||
node_modules: package-lock.json
|
||||
npm install --no-save
|
||||
@touch node_modules
|
||||
|
||||
.venv: uv.lock
|
||||
uv sync
|
||||
.venv: poetry.lock
|
||||
poetry install
|
||||
@touch .venv
|
||||
|
||||
.PHONY: update
|
||||
update: update-go update-js update-py ## update dependencies
|
||||
|
||||
.PHONY: update-go
|
||||
update-go: ## update go dependencies
|
||||
$(GO) get -u ./...
|
||||
$(MAKE) tidy
|
||||
update: update-js update-py ## update js and py dependencies
|
||||
|
||||
.PHONY: update-js
|
||||
update-js: node_modules ## update js dependencies
|
||||
pnpm exec updates -u -f package.json
|
||||
rm -rf node_modules pnpm-lock.yaml
|
||||
pnpm install
|
||||
@touch node_modules
|
||||
$(MAKE) --no-print-directory nolyfill
|
||||
|
||||
.PHONY: nolyfill
|
||||
nolyfill: node_modules ## apply nolyfill overrides to package.json and relock
|
||||
pnpm exec nolyfill install
|
||||
pnpm install
|
||||
update-js: node-check | node_modules ## update js dependencies
|
||||
npx updates -u -f package.json
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install --package-lock
|
||||
npx nolyfill install
|
||||
npm install --package-lock
|
||||
@touch node_modules
|
||||
|
||||
.PHONY: update-py
|
||||
update-py: node_modules ## update py dependencies
|
||||
pnpm exec updates -u -f pyproject.toml
|
||||
rm -rf .venv uv.lock
|
||||
uv sync
|
||||
update-py: node-check | node_modules ## update py dependencies
|
||||
npx updates -u -f pyproject.toml
|
||||
rm -rf .venv poetry.lock
|
||||
poetry install
|
||||
@touch .venv
|
||||
|
||||
.PHONY: vite
|
||||
vite: $(FRONTEND_DEST) ## build vite files
|
||||
.PHONY: webpack
|
||||
webpack: $(WEBPACK_DEST) ## build webpack files
|
||||
|
||||
$(FRONTEND_DEST): $(FRONTEND_SOURCES) $(FRONTEND_CONFIGS) pnpm-lock.yaml
|
||||
@$(MAKE) -s node_modules
|
||||
@rm -rf $(FRONTEND_DEST_ENTRIES)
|
||||
@echo "Running vite build..."
|
||||
@pnpm exec vite build
|
||||
@touch $(FRONTEND_DEST)
|
||||
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
|
||||
@$(MAKE) -s node-check node_modules
|
||||
@rm -rf $(WEBPACK_DEST_ENTRIES)
|
||||
@echo "Running webpack..."
|
||||
@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
|
||||
@touch $(WEBPACK_DEST)
|
||||
|
||||
.PHONY: svg
|
||||
svg: node_modules ## build svg files
|
||||
svg: node-check | node_modules ## build svg files
|
||||
rm -rf $(SVG_DEST_DIR)
|
||||
node tools/generate-svg.ts
|
||||
node tools/generate-svg.js
|
||||
|
||||
.PHONY: svg-check
|
||||
svg-check: svg
|
||||
@@ -650,22 +872,33 @@ svg-check: svg
|
||||
|
||||
.PHONY: lockfile-check
|
||||
lockfile-check:
|
||||
pnpm install --frozen-lockfile
|
||||
@diff=$$(git diff --color=always pnpm-lock.yaml); \
|
||||
npm install --package-lock-only
|
||||
@diff=$$(git diff --color=always package-lock.json); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "pnpm-lock.yaml is inconsistent with package.json"; \
|
||||
echo "Please run 'pnpm install --frozen-lockfile' and commit the result:"; \
|
||||
echo "package-lock.json is inconsistent with package.json"; \
|
||||
echo "Please run 'npm install --package-lock-only' and commit the result:"; \
|
||||
printf "%s" "$${diff}"; \
|
||||
exit 1; \
|
||||
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
|
||||
generate-gitignore: ## update gitignore files
|
||||
$(GO) run build/generate-gitignores.go
|
||||
|
||||
.PHONY: generate-images
|
||||
generate-images: | node_modules ## generate images
|
||||
cd tools && node generate-images.ts $(TAGS)
|
||||
generate-images: | node_modules
|
||||
npm install --no-save fabric@6 imagemin-zopfli@7
|
||||
node tools/generate-images.js $(TAGS)
|
||||
|
||||
.PHONY: generate-manpage
|
||||
generate-manpage: ## generate manpage
|
||||
@@ -675,6 +908,14 @@ generate-manpage: ## generate manpage
|
||||
@gzip -9 man/man1/gitea.1 && echo man/man1/gitea.1.gz created
|
||||
@#TODO A small script that formats config-cheat-sheet.en-us.md nicely for use as a config man page
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
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" .
|
||||
|
||||
# This endif closes the if at the top of the file
|
||||
endif
|
||||
|
||||
# Disable parallel execution because it would break some targets that don't
|
||||
# specify exact dependencies like 'backend' which does currently not depend
|
||||
# on 'frontend' to enable Node.js-less builds from source tarballs.
|
||||
|
||||
@@ -1,43 +1,213 @@
|
||||
# MokoGitea
|
||||
# Gitea
|
||||
|
||||
Moko fork of Gitea — adding project board REST API endpoints and custom enhancements
|
||||
[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
|
||||
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||
[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
|
||||
[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
|
||||
[](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
|
||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||
[](https://translate.gitea.com "Crowdin")
|
||||
|
||||
  
|
||||
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md)
|
||||
|
||||
## Purpose
|
||||
|
||||
Custom Gitea fork with Project Board API
|
||||
The goal of this project is to make the easiest, fastest, and most
|
||||
painless way of setting up a self-hosted Git service.
|
||||
|
||||
---
|
||||
As Gitea is written in Go, it works across **all** the platforms and
|
||||
architectures that are supported by Go, including Linux, macOS, and
|
||||
Windows on x86, amd64, ARM and PowerPC architectures.
|
||||
This project has been
|
||||
[forked](https://blog.gitea.com/welcome-to-gitea/) from
|
||||
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
|
||||
|
||||
## Pages
|
||||
For online demonstrations, you can visit [demo.gitea.com](https://demo.gitea.com).
|
||||
|
||||
- [Branding](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Branding)
|
||||
- [Deployment](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/Deployment)
|
||||
- [Project API](Project API)
|
||||
- [roadmap](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki/roadmap)
|
||||
For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
|
||||
|
||||
---
|
||||
|
||||
**Category:** Infrastructure | **Platform:** [moko-platform wiki](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki)
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation is available on the [Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki).
|
||||
You can find comprehensive documentation on our official [documentation website](https://docs.gitea.com/).
|
||||
|
||||
It includes installation, administration, usage, development, contributing guides, and more to help you get started and explore all features effectively.
|
||||
|
||||
If you have any suggestions or would like to contribute to it, you can visit the [documentation repository](https://gitea.com/gitea/docs)
|
||||
|
||||
## Building
|
||||
|
||||
From the root of the source tree, run:
|
||||
|
||||
TAGS="bindata" make build
|
||||
|
||||
or if SQLite support is required:
|
||||
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||
|
||||
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 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.
|
||||
|
||||
More info: https://docs.gitea.com/installation/install-from-source
|
||||
|
||||
## Using
|
||||
|
||||
After building, a binary file named `gitea` will be generated in the root of the source tree by default. To run it, use:
|
||||
|
||||
./gitea web
|
||||
|
||||
> [!NOTE]
|
||||
> If you're interested in using our APIs, we have experimental support with [documentation](https://docs.gitea.com/api).
|
||||
|
||||
## Contributing
|
||||
|
||||
See the wiki for development guidelines and contribution instructions.
|
||||
Expected workflow is: Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
|
||||
> 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
|
||||
|
||||
## Translating
|
||||
|
||||
[](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.
|
||||
|
||||
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).
|
||||
|
||||
## Official and Third-Party Projects
|
||||
|
||||
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/act_runner) for Gitea Action.
|
||||
|
||||
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more.
|
||||
|
||||
## Communication
|
||||
|
||||
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||
|
||||
If you have questions that are not covered by the [documentation](https://docs.gitea.com/), you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
|
||||
|
||||
## Authors
|
||||
|
||||
- [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
- [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||
- [Translators](options/locale/TRANSLATORS)
|
||||
|
||||
## Backers
|
||||
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/gitea#backer)]
|
||||
|
||||
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
|
||||
|
||||
## Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/gitea#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
|
||||
|
||||
## FAQ
|
||||
|
||||
**How do you pronounce Gitea?**
|
||||
|
||||
Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
|
||||
|
||||
**Why is this not hosted on a Gitea instance?**
|
||||
|
||||
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
||||
|
||||
**Where can I find the security patches?**
|
||||
|
||||
In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0 or later -- see the [LICENSE](LICENSE) file.
|
||||
This project is licensed under the MIT License.
|
||||
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
|
||||
for the full license text.
|
||||
|
||||
---
|
||||
## Further information
|
||||
|
||||
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
|
||||
<details>
|
||||
<summary>Looking for an overview of the interface? Check it out!</summary>
|
||||
|
||||
### Login/Register Page
|
||||
|
||||

|
||||

|
||||
|
||||
### User Dashboard
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### User Profile
|
||||
|
||||

|
||||
|
||||
### Explore
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### Repository
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Repository Issue
|
||||
|
||||

|
||||

|
||||
|
||||
#### Repository Pull Requests
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### Repository Actions
|
||||
|
||||

|
||||

|
||||
|
||||
#### Repository Activity
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### Organization
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
+6
-1
@@ -8,6 +8,7 @@
|
||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||
[](https://translate.gitea.com "Crowdin")
|
||||
|
||||
[English](./README.md) | [繁體中文](./README.zh-tw.md)
|
||||
@@ -38,10 +39,14 @@
|
||||
|
||||
TAGS="bindata" make build
|
||||
|
||||
如果需要 SQLite 支持:
|
||||
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||
|
||||
`build` 目标分为两个子目标:
|
||||
|
||||
- `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 的情况下构建。
|
||||
|
||||
|
||||
+6
-1
@@ -8,6 +8,7 @@
|
||||
[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
|
||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||
[](https://translate.gitea.com "Crowdin")
|
||||
|
||||
[English](./README.md) | [简体中文](./README.zh-cn.md)
|
||||
@@ -38,10 +39,14 @@
|
||||
|
||||
TAGS="bindata" make build
|
||||
|
||||
如果需要 SQLite 支援:
|
||||
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||
|
||||
`build` 目標分為兩個子目標:
|
||||
|
||||
- `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 的情況下構建。
|
||||
|
||||
|
||||
+27
-27
@@ -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.
|
||||
|
||||
The PGP key is valid until July 4, 2026.
|
||||
The PGP key is valid until July 9, 2025.
|
||||
|
||||
```
|
||||
Key ID: 6FCD2D5B
|
||||
Key Type: RSA
|
||||
Expires: 7/4/2026
|
||||
Expires: 7/9/2025
|
||||
Key Size: 4096/4096
|
||||
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
|
||||
```
|
||||
@@ -42,18 +42,18 @@ lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e
|
||||
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
|
||||
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBAhsD
|
||||
BQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAFiEEPeA9HhRKfwaTWZncqv0jgW/N
|
||||
LVsFAmhoHmkFCQeT6esACgkQqv0jgW/NLVuFLRAAmjBQSKRAgs2bFIEj7HLAbDp4
|
||||
f+XkdH+GsT3jRPOZ9QZgmtM+TfoE4yNgIVfOl+s4RdjM/W4QzqZuPQ55hbEHd056
|
||||
cJmm7B+6GsHFcdrPmh65sOCEIyh4+t45dUfeWpFsDPqm9j1UHXAJQIpB8vDEVAPH
|
||||
t+3wLCk8GMPJs1o5tIyMmaO23ngvkwn8eG7KgY+rp2PzObrb5g7ppci0ILzILkrp
|
||||
HVjZsEfUWRgSVF7LuU5ppqDKrlcqwUpQq6n3kGMZcLrCp6ACKP04TBmTfUxNwdL7
|
||||
I0N7apI2Pbct9T1Gv/lYAUFWyU2c3gh/EBLbO6BukaLOFRQHrtNfdJV/YnMPlcXr
|
||||
LUJjK9K4eAH9DsrZqrisz/LthsC2BaNIN3KRMTk5YTYgmIh8GXzSgihORmtDFELC
|
||||
RroID3pTuS0zjXh+wpY9GuPTh7UW23p42Daxca4fAT4k5EclvDRUrL21xMopPMiL
|
||||
HuNdELz4FVchRTy05PjzKVyjVInDNojE2KUxnjxZDzYJ6aT/g+coD5yfntYm8BEj
|
||||
+ZzL0ndZES54hzKLpv7zwBQwFzam68clZYmDPILOPTflQDfpGEWmJK4undFU5obz
|
||||
ZsQRz0R3ulspChATbZxO0d5LX2obLpKO9X3b5VoO1KF+R8Vjw1Y0KxrNZ6rIcfqH
|
||||
Z50QVQKSe9dm08K0ON+5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
|
||||
LVsFAmaMse0FCQW4fW8ACgkQqv0jgW/NLVtXLg/+PF4G9Jhlui15BTNlEBJAV2P/
|
||||
1QlAV2krk0fP7tykn0FR9RfGIfVV/kwC1f+ouosYPQDDevl9LWdUIM+g94DtNo2o
|
||||
7ACpcL3morvt5lVGpIZHL8TbX0qmFRXL/pB/cB+K6IwYvh2mrbp2zH+r4SCRyFYq
|
||||
BjgXYFTI1MylJ1ShAjU6Z+m3oJ+2xs5LzHS0X6zkTjzA2Zl4zQzciQ9T+wJcE7Zi
|
||||
HXdM1+YMF8KGNP8J9Rpug5oNDJ98lgZirRY7c3A/1xmYBiPnULwuuymdqEZO7l70
|
||||
SeAlE1RWYX8kbOBnBb/KY4XwE3Vic1oEzc9DiPWVH1ElX86WNNsFzuyULiwoBoWg
|
||||
pqZGhL9x1p5+46RGQSDczsHM7YGVtfYOiDo2PAVrmwsT0BnXnK8Oe3YIkvmUPEJu
|
||||
OkLt0Z6A5n8pz8zhQzuApwBsK4ncJ8zTCpvz/pfKKqZC/Vnoh3gKGhDGvOZ+b5IJ
|
||||
0kUTe2JsbnwFixDUMDtacQ1op8XOyLoLVmgqLn0+Pws4XPBlMof2bioFir3yHKnP
|
||||
gNchsF1agrlSIo5GA8u4ga+IlCSfvFIKrl7+cxacKcJYt/vbOU5KcvVJI5EtHKCG
|
||||
xfHjHY2ah1Qww7SxW6IXiRZZzPpsL2mBM2CD7N3qh9bV2s27wxYCdUodsIZbiyHe
|
||||
oWPzfBnkmiAN8KlZxHm5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
|
||||
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
|
||||
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
|
||||
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
|
||||
@@ -65,19 +65,19 @@ s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/
|
||||
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
|
||||
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
|
||||
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYCGwwWIQQ94D0eFEp/BpNZmdyq/SOBb80t
|
||||
WwUCaGgeJAUJB5PppgAKCRCq/SOBb80tW/NWEACB6Jrf0gWlk7e+hNCdnbM0ZVWU
|
||||
f2sHNFfXxxsdhpcDgKbNHtkZb8nZgv8AX+5fTtUwMVa3vKcdw30xFiIM5N7cCIPV
|
||||
vg/5z5BtfEaitnabEUG2iiVDIy8IHXIcK10rX+7BosA3QDl2PsiBHwyi5G13lRk8
|
||||
zGTSNDuOalug33h5/lr2dPigamkq74Aoy29q8Rjad6GfWHipL2bFimgtY+Zdi0BH
|
||||
NLk4EJXxj1SgVx5dtkQzWJReBA5M+FQ4QYQZBO+f4TDoOLmjui152uhkoLBQbGAa
|
||||
WWJFTVxm0bG5MXloEL3gA8DfU7XDwuW/sHJC5pBko8RpQViooOhckMepZV3Y83DK
|
||||
bwLYa3JmPgj2rEv4993dvrJbQhpGd082HOxOsllCs8pgNq1SnXpWYfcGTgGKC3ts
|
||||
U8YZUUJUQ7mi2L8Tv3ix20c9EiGmA30JAmA8eZTC3cWup91ZkkVBFRml2czTXajd
|
||||
RWZ6GbHV5503ueDQcB8yBVgF3CSixs67+dGSbD3p86OqGrjAcJzM5TFbNKcnGLdE
|
||||
kGbZpNwAISy750lXzXKmyrh5RTCeTOQerbwCMBvHZO+HAevA/LXDTw2OAiSIQlP5
|
||||
sYA4sFYLQ30OAkgJcmdp/pSgVj/erNtSN07ClrOpDb/uFpQymO6K2h0Pst3feNVK
|
||||
9M2VbqL9C51z/wyHLg==
|
||||
=SfZA
|
||||
WwUCZoyyjQUJBbh+DwAKCRCq/SOBb80tW18XD/9MXztmf01MT+1kZdBouZ/7Rp/7
|
||||
9kuqo//B1G+RXau4oFtPqb67kNe2WaIc3u5B73PUHsMf3i6z4ib2KbMhZZerLn0O
|
||||
dRglcuPeNWmsASY3dH/XVG0cT0zvvWegagd12TJEl3Vs+7XNrOw4cwDj9L1+GH9m
|
||||
kSt4uaANWn/6a3RvMRhiVEYuNwhAzcKaactPmYqrLJgoVLbRSDkgyHaMQ2jKgLxk
|
||||
ifS/fvluGV0ub2Po6DJiqfRpd1tDvPhe9y1+r1WFDZsOcvTcZUfSt/7dXMGfqGu0
|
||||
2daVFlfeSXSALrDE5uc0UxodHCpP3sqRYDZevGLBRaaTkIjYXG/+N898+7K5WJF4
|
||||
xXOLWxM2cwGkG7eC9pugcDnBp9XlF7O+GBiZ05JUe5flXDQFZ+h3exjopu6KHF1B
|
||||
RnzNy8LC0UKb+AuvRIOLV92a9Q9wGWU/jaVDu6nZ0umAeuSzxiHoDsonm0Fl9QAz
|
||||
2/xCokebuoeLrEK7R2af3X86mqq3sVO4ax+HPYChzOaVQBiHUW/TAldWcldYYphR
|
||||
/e2WsbmQfvCRtz/bZfo+aUVnrHNjzVMtF2SszdVmA/04Y8pS28MqtuRqhm5DPOOd
|
||||
g1YeUywK5jRZ1twyo1kzJEFPLaoeaXaycsR1PMVBW0Urik5mrR/pOWq7PPoZoKb2
|
||||
lXYLE8bwkuQTmsyL1g==
|
||||
=9i7d
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
```
|
||||
|
||||
Generated
+1
-11483
File diff suppressed because one or more lines are too long
Generated
+168
-313
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
)
|
||||
@@ -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("----")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+75
-10
@@ -6,22 +6,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/assetfs"
|
||||
"github.com/shurcooL/vfsgen"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 3 {
|
||||
fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}")
|
||||
os.Exit(1)
|
||||
func needsUpdate(dir, filename string) (bool, []byte) {
|
||||
needRegen := false
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
needRegen = true
|
||||
}
|
||||
|
||||
dir, filename := os.Args[1], os.Args[2]
|
||||
fmt.Printf("generating bindata for %s to %s\n", dir, filename)
|
||||
if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil {
|
||||
fmt.Printf("failed: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
oldHash, err := os.ReadFile(filename + ".hash")
|
||||
if err != nil {
|
||||
oldHash = []byte{}
|
||||
}
|
||||
|
||||
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 (
|
||||
gemojiURL = "https://raw.githubusercontent.com/rhysd/gemoji/537ff2d7e0496e9964824f7f73ec7ece88c9765a/db/emoji.json"
|
||||
maxUnicodeVersion = 16
|
||||
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
||||
maxUnicodeVersion = 15
|
||||
)
|
||||
|
||||
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)
|
||||
file, _ := json.MarshalIndent(data, "", " ")
|
||||
_ = os.WriteFile("assets/emoji.json", append(file, '\n'), 0o644)
|
||||
file, _ := json.Marshal(data)
|
||||
_ = os.WriteFile("assets/emoji.json", file, 0o644)
|
||||
|
||||
// Add skin tones to emoji that support it
|
||||
var (
|
||||
|
||||
+62
-183
@@ -8,220 +8,99 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
)
|
||||
|
||||
// regexp is based on go-license, excluding README and NOTICE
|
||||
// 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).*$`)
|
||||
|
||||
// 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 {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
LicenseText string `json:"licenseText"`
|
||||
}
|
||||
|
||||
// getModules returns all dependency modules with their local directory paths
|
||||
// and the package directories used from each module.
|
||||
func getModules(goCmd string) []ModuleInfo {
|
||||
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)
|
||||
func main() {
|
||||
if len(os.Args) != 3 {
|
||||
fmt.Println("usage: go run generate-go-licenses.go <base-dir> <out-json-file>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var modules []ModuleInfo
|
||||
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
|
||||
}
|
||||
base, out := os.Args[1], os.Args[2]
|
||||
|
||||
// findLicenseFiles scans a module's root directory and its used package
|
||||
// directories for license files. It also walks up from each package directory
|
||||
// to the module root, scanning intermediate directories. Subdirectory licenses
|
||||
// are only included if their text differs from the root license(s).
|
||||
func findLicenseFiles(mod ModuleInfo) []LicenseEntry {
|
||||
var entries []LicenseEntry
|
||||
seenTexts := make(map[string]bool)
|
||||
// Add ext for excluded files because license_test.go will be included for some reason.
|
||||
// And there are more files that should be excluded, check with:
|
||||
//
|
||||
// go run github.com/google/go-licenses@v1.6.0 save . --force --save_path=.go-licenses 2>/dev/null
|
||||
// find .go-licenses -type f | while read FILE; do echo "${$(basename $FILE)##*.}"; done | sort -u
|
||||
// AUTHORS
|
||||
// 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.
|
||||
entries = append(entries, scanDirForLicenses(mod.Dir, mod.Path, "")...)
|
||||
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))
|
||||
var paths []string
|
||||
err := filepath.WalkDir(base, func(path string, entry fs.DirEntry, err error) error {
|
||||
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
|
||||
entryPath := modulePath + "/" + name
|
||||
if moduleRoot != "" {
|
||||
rel, _ := filepath.Rel(moduleRoot, dir)
|
||||
if rel != "." {
|
||||
relSlash := filepath.ToSlash(rel)
|
||||
entryName = modulePath + "/" + relSlash
|
||||
entryPath = modulePath + "/" + relSlash + "/" + name
|
||||
}
|
||||
pkgPath := filepath.ToSlash(filePath)
|
||||
pkgPath = strings.TrimPrefix(pkgPath, base+"/")
|
||||
pkgName := path.Dir(pkgPath)
|
||||
|
||||
// There might be a bug somewhere in go-licenses that sometimes interprets the
|
||||
// root package as "." and sometimes as "code.gitea.io/gitea". Workaround by
|
||||
// removing both of them for the sake of stable output.
|
||||
if pkgName == "." || pkgName == "code.gitea.io/gitea" {
|
||||
continue
|
||||
}
|
||||
|
||||
entries = append(entries, LicenseEntry{
|
||||
Name: entryName,
|
||||
Path: entryPath,
|
||||
LicenseText: string(content),
|
||||
Name: pkgName,
|
||||
Path: pkgPath,
|
||||
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, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// generate-openapi converts Gitea's Swagger 2.0 spec into an OpenAPI 3.0 spec.
|
||||
//
|
||||
// Gitea generates a Swagger 2.0 spec from code annotations (make generate-swagger).
|
||||
// This tool converts it to OAS3 so that SDK generators and tools that require
|
||||
// OAS3 (e.g. progenitor for Rust) can consume it directly. The conversion also
|
||||
// deduplicates inline enum definitions into named schema components, producing
|
||||
// cleaner SDK output with proper enum types instead of anonymous strings.
|
||||
//
|
||||
// Run: go run build/generate-openapi.go
|
||||
// Output: templates/swagger/v1_openapi3_json.tmpl
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/build/openapi3gen"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
const (
|
||||
swaggerSpecPath = "templates/swagger/v1_json.tmpl"
|
||||
openapi3OutPath = "templates/swagger/v1_openapi3_json.tmpl"
|
||||
|
||||
appSubUrlVar = "{{.SwaggerAppSubUrl}}"
|
||||
appVerVar = "{{.SwaggerAppVer}}"
|
||||
|
||||
appSubUrlPlaceholder = "GITEA_APP_SUB_URL_PLACEHOLDER"
|
||||
appVerPlaceholder = "0.0.0-gitea-placeholder"
|
||||
)
|
||||
|
||||
var (
|
||||
appSubUrlRe = regexp.MustCompile(regexp.QuoteMeta(appSubUrlVar))
|
||||
appVerRe = regexp.MustCompile(regexp.QuoteMeta(appVerVar))
|
||||
|
||||
enumScanDirs = []string{
|
||||
"modules/structs",
|
||||
"modules/commitstatus",
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
astEnumMap, err := openapi3gen.ScanSwaggerEnumTypes(enumScanDirs)
|
||||
if err != nil {
|
||||
log.Fatalf("scanning swagger:enum annotations: %v", err)
|
||||
}
|
||||
names := make([]string, 0, len(astEnumMap))
|
||||
for _, n := range astEnumMap {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
fmt.Fprintf(os.Stderr, "discovered %d swagger:enum types: %s\n", len(names), strings.Join(names, ", "))
|
||||
|
||||
data, err := os.ReadFile(swaggerSpecPath)
|
||||
if err != nil {
|
||||
log.Fatalf("reading swagger spec: %v", err)
|
||||
}
|
||||
|
||||
cleaned := appSubUrlRe.ReplaceAll(data, []byte(appSubUrlPlaceholder))
|
||||
cleaned = appVerRe.ReplaceAll(cleaned, []byte(appVerPlaceholder))
|
||||
|
||||
oas3, err := openapi3gen.Convert(cleaned, astEnumMap)
|
||||
if err != nil {
|
||||
log.Fatalf("converting to openapi 3.0: %v", err)
|
||||
}
|
||||
|
||||
oas3.Servers = openapi3.Servers{
|
||||
{URL: appSubUrlPlaceholder + "/api/v1"},
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(oas3, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("marshaling openapi 3.0: %v", err)
|
||||
}
|
||||
|
||||
result := strings.ReplaceAll(string(out), appSubUrlPlaceholder, appSubUrlVar)
|
||||
result = strings.ReplaceAll(result, appVerPlaceholder, appVerVar)
|
||||
result = strings.TrimSpace(result)
|
||||
|
||||
if err := os.WriteFile(openapi3OutPath, []byte(result), 0o644); err != nil {
|
||||
log.Fatalf("writing openapi 3.0 spec: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Generated %s\n", openapi3OutPath)
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi2"
|
||||
"github.com/getkin/kin-openapi/openapi2conv"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
// rxDeprecated matches "deprecated" as a word at the start of a description
|
||||
// or preceded by whitespace/punctuation that indicates a leading marker (e.g.
|
||||
// "Deprecated: true", "deprecated (use X instead)"). Rejects negated phrases
|
||||
// like "not deprecated" or "previously deprecated, now supported".
|
||||
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
||||
|
||||
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
|
||||
// Gitea-specific post-processing: file-schema fixups, URI formats,
|
||||
// deprecated flags, and shared-enum extraction.
|
||||
//
|
||||
// astEnumMap is a value-set-key → Go-type-name map (built by
|
||||
// ScanSwaggerEnumTypes). If a shared enum in the spec has no entry in the
|
||||
// map, Convert returns an error — no fallback naming.
|
||||
func Convert(swaggerJSON []byte, astEnumMap map[string]string) (*openapi3.T, error) {
|
||||
var swagger2 openapi2.T
|
||||
if err := json.Unmarshal(swaggerJSON, &swagger2); err != nil {
|
||||
return nil, fmt.Errorf("parsing swagger 2.0: %w", err)
|
||||
}
|
||||
|
||||
oas3, err := openapi2conv.ToV3(&swagger2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to openapi 3.0: %w", err)
|
||||
}
|
||||
|
||||
fixFileSchemas(oas3)
|
||||
addURIFormats(oas3)
|
||||
addDeprecatedFlags(oas3)
|
||||
if err := extractSharedEnums(oas3, astEnumMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return oas3, nil
|
||||
}
|
||||
|
||||
func fixFileSchemas(doc *openapi3.T) {
|
||||
for _, pathItem := range doc.Paths.Map() {
|
||||
for _, op := range []*openapi3.Operation{
|
||||
pathItem.Get, pathItem.Post, pathItem.Put, pathItem.Patch,
|
||||
pathItem.Delete, pathItem.Head, pathItem.Options, pathItem.Trace,
|
||||
} {
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
for _, resp := range op.Responses.Map() {
|
||||
if resp.Value == nil {
|
||||
continue
|
||||
}
|
||||
for _, mediaType := range resp.Value.Content {
|
||||
fixSchema(mediaType.Schema)
|
||||
}
|
||||
}
|
||||
if op.RequestBody != nil && op.RequestBody.Value != nil {
|
||||
for _, mediaType := range op.RequestBody.Value.Content {
|
||||
fixSchema(mediaType.Schema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixSchema rewrites any "type: file" schemas to the OAS3 equivalent
|
||||
// (type: string, format: binary), recursing into Properties, Items, and
|
||||
// AllOf/OneOf/AnyOf/Not branches. $ref nodes are skipped so shared schemas
|
||||
// are rewritten exactly once when visited through their declaration.
|
||||
func fixSchema(ref *openapi3.SchemaRef) {
|
||||
if ref == nil || ref.Value == nil || ref.Ref != "" {
|
||||
return
|
||||
}
|
||||
s := ref.Value
|
||||
if s.Type.Is("file") {
|
||||
s.Type = &openapi3.Types{"string"}
|
||||
s.Format = "binary"
|
||||
}
|
||||
for _, p := range s.Properties {
|
||||
fixSchema(p)
|
||||
}
|
||||
fixSchema(s.Items)
|
||||
for _, sub := range s.AllOf {
|
||||
fixSchema(sub)
|
||||
}
|
||||
for _, sub := range s.OneOf {
|
||||
fixSchema(sub)
|
||||
}
|
||||
for _, sub := range s.AnyOf {
|
||||
fixSchema(sub)
|
||||
}
|
||||
fixSchema(s.Not)
|
||||
}
|
||||
|
||||
// addURIFormats sets format: uri on string properties whose names indicate
|
||||
// they hold URLs. This information is lost in Swagger 2.0 but is valuable
|
||||
// for code generators.
|
||||
func addURIFormats(doc *openapi3.T) {
|
||||
if doc.Components == nil {
|
||||
return
|
||||
}
|
||||
for _, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for propName, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
prop := propRef.Value
|
||||
if !prop.Type.Is("string") || prop.Format != "" {
|
||||
continue
|
||||
}
|
||||
if isURLProperty(propName) {
|
||||
prop.Format = "uri"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isURLProperty(name string) bool {
|
||||
if strings.HasSuffix(name, "_url") {
|
||||
return true
|
||||
}
|
||||
switch name {
|
||||
case "url", "html_url", "clone_url":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addDeprecatedFlags sets deprecated: true on schema properties whose
|
||||
// description starts with a "deprecated" marker (e.g. "Deprecated: true"
|
||||
// or "deprecated (use X instead)"). Does not match negated phrases.
|
||||
func addDeprecatedFlags(doc *openapi3.T) {
|
||||
if doc.Components == nil {
|
||||
return
|
||||
}
|
||||
for _, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for _, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
if rxDeprecated.MatchString(propRef.Value.Description) {
|
||||
propRef.Value.Deprecated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type enumUsage struct {
|
||||
schemaName string
|
||||
propName string
|
||||
propRef *openapi3.SchemaRef
|
||||
inItems bool
|
||||
}
|
||||
|
||||
// extractSharedEnums finds identical enum arrays used by multiple schema
|
||||
// properties, creates a standalone named schema for each, and replaces
|
||||
// the inline enums with $ref pointers.
|
||||
//
|
||||
// If the derived enum name collides with an existing component schema, or
|
||||
// no // swagger:enum annotation matches the value set, generation aborts
|
||||
// with an actionable error — there are no silent fallbacks.
|
||||
func extractSharedEnums(doc *openapi3.T, astEnumMap map[string]string) error {
|
||||
if doc.Components == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
enumGroups := map[string][]enumUsage{}
|
||||
|
||||
for schemaName, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for propName, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
if len(propRef.Value.Enum) > 1 && propRef.Value.Type.Is("string") {
|
||||
key := EnumKey(propRef.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, false})
|
||||
}
|
||||
if propRef.Value.Type.Is("array") && propRef.Value.Items != nil &&
|
||||
propRef.Value.Items.Value != nil && propRef.Value.Items.Ref == "" &&
|
||||
len(propRef.Value.Items.Value.Enum) > 1 && propRef.Value.Items.Value.Type.Is("string") {
|
||||
key := EnumKey(propRef.Value.Items.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, usages := range enumGroups {
|
||||
if len(usages) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
enumName, err := deriveEnumName(key, usages, astEnumMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exists := doc.Components.Schemas[enumName]; exists {
|
||||
return fmt.Errorf("enum name collision: %s already exists as a component schema", enumName)
|
||||
}
|
||||
|
||||
var enumValues []any
|
||||
if usages[0].inItems {
|
||||
enumValues = usages[0].propRef.Value.Items.Value.Enum
|
||||
} else {
|
||||
enumValues = usages[0].propRef.Value.Enum
|
||||
}
|
||||
|
||||
doc.Components.Schemas[enumName] = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: enumValues,
|
||||
},
|
||||
}
|
||||
|
||||
ref := "#/components/schemas/" + enumName
|
||||
|
||||
for _, usage := range usages {
|
||||
if usage.inItems {
|
||||
usage.propRef.Value.Items = &openapi3.SchemaRef{Ref: ref}
|
||||
} else {
|
||||
old := usage.propRef.Value
|
||||
if old.Description == "" && !old.Deprecated && old.Format == "" {
|
||||
usage.propRef.Ref = ref
|
||||
usage.propRef.Value = nil
|
||||
} else {
|
||||
usage.propRef.Value = &openapi3.Schema{
|
||||
AllOf: openapi3.SchemaRefs{
|
||||
{Ref: ref},
|
||||
},
|
||||
Description: old.Description,
|
||||
Deprecated: old.Deprecated,
|
||||
Format: old.Format,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deriveEnumName looks up a shared enum's Go type name from astEnumMap by
|
||||
// value-set key. If no annotation matches, returns an error identifying the
|
||||
// offending properties and the fix.
|
||||
func deriveEnumName(key string, usages []enumUsage, astEnumMap map[string]string) (string, error) {
|
||||
if name, ok := astEnumMap[key]; ok {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
props := map[string]bool{}
|
||||
for _, u := range usages {
|
||||
props[fmt.Sprintf("%s.%s", u.schemaName, u.propName)] = true
|
||||
}
|
||||
propList := make([]string, 0, len(props))
|
||||
for p := range props {
|
||||
propList = append(propList, p)
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"no swagger:enum annotation matches value-set %q used by %d properties: %v; "+
|
||||
"fix by adding a named string type with // swagger:enum to modules/structs or modules/commitstatus",
|
||||
key, len(usages), propList,
|
||||
)
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
func TestDeriveEnumName_hit(t *testing.T) {
|
||||
key := EnumKey([]any{"red", "green", "blue"})
|
||||
astMap := map[string]string{key: "Color"}
|
||||
usages := []enumUsage{{schemaName: "Paint", propName: "color"}}
|
||||
got, err := deriveEnumName(key, usages, astMap)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "Color" {
|
||||
t.Fatalf("got %q, want %q", got, "Color")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveEnumName_miss(t *testing.T) {
|
||||
key := EnumKey([]any{"x", "y"})
|
||||
usages := []enumUsage{{schemaName: "Thing", propName: "kind"}}
|
||||
_, err := deriveEnumName(key, usages, map[string]string{})
|
||||
if err == nil {
|
||||
t.Fatal("expected miss error, got nil")
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "Thing.kind") {
|
||||
t.Fatalf("error %q should list the missing usage", msg)
|
||||
}
|
||||
if !strings.Contains(msg, "swagger:enum") {
|
||||
t.Fatalf("error %q should hint at the fix", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSharedEnums_usesASTMap(t *testing.T) {
|
||||
doc := &openapi3.T{
|
||||
Components: &openapi3.Components{
|
||||
Schemas: openapi3.Schemas{
|
||||
"A": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green", "blue"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
"B": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green", "blue"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
astMap := map[string]string{EnumKey([]any{"red", "green", "blue"}): "Color"}
|
||||
if err := extractSharedEnums(doc, astMap); err != nil {
|
||||
t.Fatalf("extractSharedEnums: %v", err)
|
||||
}
|
||||
if _, ok := doc.Components.Schemas["Color"]; !ok {
|
||||
t.Fatalf("expected Color schema to be extracted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixFileSchemas_recursesIntoNested(t *testing.T) {
|
||||
fileType := func() *openapi3.SchemaRef {
|
||||
return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"file"}}}
|
||||
}
|
||||
doc := &openapi3.T{
|
||||
Paths: openapi3.NewPaths(),
|
||||
}
|
||||
doc.Paths.Set("/upload", &openapi3.PathItem{
|
||||
Post: &openapi3.Operation{
|
||||
RequestBody: &openapi3.RequestBodyRef{
|
||||
Value: &openapi3.RequestBody{
|
||||
Content: openapi3.Content{
|
||||
"multipart/form-data": {
|
||||
Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"attachment": fileType(),
|
||||
"items": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"array"},
|
||||
Items: fileType(),
|
||||
}},
|
||||
"alt": {Value: &openapi3.Schema{
|
||||
AllOf: openapi3.SchemaRefs{fileType()},
|
||||
}},
|
||||
"one": {Value: &openapi3.Schema{
|
||||
OneOf: openapi3.SchemaRefs{fileType()},
|
||||
}},
|
||||
"any": {Value: &openapi3.Schema{
|
||||
AnyOf: openapi3.SchemaRefs{fileType()},
|
||||
}},
|
||||
"not": {Value: &openapi3.Schema{
|
||||
Not: fileType(),
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Responses: openapi3.NewResponses(),
|
||||
},
|
||||
})
|
||||
|
||||
fixFileSchemas(doc)
|
||||
|
||||
props := doc.Paths.Value("/upload").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties
|
||||
if !props["attachment"].Value.Type.Is("string") || props["attachment"].Value.Format != "binary" {
|
||||
t.Errorf("nested property not fixed: %+v", props["attachment"].Value)
|
||||
}
|
||||
if !props["items"].Value.Items.Value.Type.Is("string") || props["items"].Value.Items.Value.Format != "binary" {
|
||||
t.Errorf("array items not fixed: %+v", props["items"].Value.Items.Value)
|
||||
}
|
||||
if !props["alt"].Value.AllOf[0].Value.Type.Is("string") || props["alt"].Value.AllOf[0].Value.Format != "binary" {
|
||||
t.Errorf("allOf branch not fixed: %+v", props["alt"].Value.AllOf[0].Value)
|
||||
}
|
||||
if !props["one"].Value.OneOf[0].Value.Type.Is("string") || props["one"].Value.OneOf[0].Value.Format != "binary" {
|
||||
t.Errorf("oneOf branch not fixed: %+v", props["one"].Value.OneOf[0].Value)
|
||||
}
|
||||
if !props["any"].Value.AnyOf[0].Value.Type.Is("string") || props["any"].Value.AnyOf[0].Value.Format != "binary" {
|
||||
t.Errorf("anyOf branch not fixed: %+v", props["any"].Value.AnyOf[0].Value)
|
||||
}
|
||||
if !props["not"].Value.Not.Value.Type.Is("string") || props["not"].Value.Not.Value.Format != "binary" {
|
||||
t.Errorf("not branch not fixed: %+v", props["not"].Value.Not.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
|
||||
doc := &openapi3.T{
|
||||
Components: &openapi3.Components{
|
||||
Schemas: openapi3.Schemas{
|
||||
"A": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
"B": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := extractSharedEnums(doc, map[string]string{}); err == nil {
|
||||
t.Fatal("expected miss error")
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package openapi3gen converts Gitea's Swagger 2.0 spec to an OpenAPI 3.0
|
||||
// spec. It discovers Go enum type names by scanning swagger:enum annotations
|
||||
// in the source tree, then names extracted shared-enum schemas accordingly.
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnumKey returns a canonical key for a set of enum values: values are
|
||||
// stringified, sorted, and joined with "|". Used to match enum value sets
|
||||
// across spec properties and scanned Go type declarations.
|
||||
func EnumKey(values []any) string {
|
||||
strs := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
strs[i] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
sort.Strings(strs)
|
||||
return strings.Join(strs, "|")
|
||||
}
|
||||
|
||||
var rxSwaggerEnum = regexp.MustCompile(`swagger:enum\s+(\w+)`)
|
||||
|
||||
// ScanSwaggerEnumTypes walks .go files under each dir and returns a map from
|
||||
// a canonical value-set key (see EnumKey) to the Go type name declared with
|
||||
// // swagger:enum TypeName.
|
||||
//
|
||||
// Returns an error on parse failure, on an annotation for a type whose
|
||||
// constants can't be extracted, or on value-set collisions between two
|
||||
// different enum types.
|
||||
func ScanSwaggerEnumTypes(dirs []string) (map[string]string, error) {
|
||||
fset := token.NewFileSet()
|
||||
parsed := []*ast.File{}
|
||||
|
||||
for _, dir := range dirs {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", dir, err)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(entry.Name(), "_test.go") {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(dir, entry.Name())
|
||||
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
parsed = append(parsed, file)
|
||||
}
|
||||
}
|
||||
|
||||
enumTypes := map[string]string{} // typeName → "" (presence marker)
|
||||
enumValues := map[string][]any{} // typeName → values
|
||||
|
||||
// Pass 1: collect every // swagger:enum TypeName declaration.
|
||||
for _, file := range parsed {
|
||||
for _, decl := range file.Decls {
|
||||
gd, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gd.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
if err := collectEnumType(gd, enumTypes); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", fset.Position(gd.Pos()).Filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: collect const values; now every annotated type is visible.
|
||||
for _, file := range parsed {
|
||||
for _, decl := range file.Decls {
|
||||
gd, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gd.Tok != token.CONST {
|
||||
continue
|
||||
}
|
||||
collectEnumValues(gd, enumTypes, enumValues)
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
for typeName := range enumTypes {
|
||||
values, ok := enumValues[typeName]
|
||||
if !ok || len(values) == 0 {
|
||||
return nil, fmt.Errorf("swagger:enum %s has no const block with typed string values", typeName)
|
||||
}
|
||||
key := EnumKey(values)
|
||||
if existing, ok := result[key]; ok && existing != typeName {
|
||||
return nil, fmt.Errorf("swagger:enum value-set collision: %s and %s both use %q", existing, typeName, key)
|
||||
}
|
||||
result[key] = typeName
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// collectEnumType scans a `type` GenDecl for // swagger:enum annotations,
|
||||
// handling both the lone form (`// swagger:enum Foo\n type Foo string`)
|
||||
// where the comment group is attached to the GenDecl, and the grouped form:
|
||||
//
|
||||
// type (
|
||||
// // swagger:enum Foo
|
||||
// Foo string
|
||||
// )
|
||||
//
|
||||
// where the comment group is attached to each TypeSpec. Caveat: Go's parser
|
||||
// only attaches a CommentGroup when it is immediately adjacent to the decl.
|
||||
// A blank line (not a `//` continuation line) between the comment and the
|
||||
// declaration drops the Doc, so annotations MUST sit directly above their
|
||||
// type. All current annotated files obey this — the rule is noted here so
|
||||
// a future edit that inserts a blank line fails fast rather than silently.
|
||||
func collectEnumType(gd *ast.GenDecl, enumTypes map[string]string) error {
|
||||
if err := registerEnumAnnotation(gd.Doc, gd.Specs, enumTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, spec := range gd.Specs {
|
||||
ts, ok := spec.(*ast.TypeSpec)
|
||||
if !ok || ts.Doc == nil {
|
||||
continue
|
||||
}
|
||||
if err := registerEnumAnnotation(ts.Doc, []ast.Spec{ts}, enumTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerEnumAnnotation(doc *ast.CommentGroup, specs []ast.Spec, enumTypes map[string]string) error {
|
||||
if doc == nil {
|
||||
return nil
|
||||
}
|
||||
matches := rxSwaggerEnum.FindStringSubmatch(doc.Text())
|
||||
if len(matches) < 2 {
|
||||
return nil
|
||||
}
|
||||
annotated := matches[1]
|
||||
for _, spec := range specs {
|
||||
ts, ok := spec.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if ts.Name.Name == annotated {
|
||||
enumTypes[annotated] = ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("swagger:enum %s: no type declaration with that name in the same decl group; check for a typo", annotated)
|
||||
}
|
||||
|
||||
func collectEnumValues(gd *ast.GenDecl, enumTypes map[string]string, enumValues map[string][]any) {
|
||||
for _, spec := range gd.Specs {
|
||||
vs, ok := spec.(*ast.ValueSpec)
|
||||
if !ok || vs.Type == nil {
|
||||
continue
|
||||
}
|
||||
ident, ok := vs.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, isEnum := enumTypes[ident.Name]; !isEnum {
|
||||
continue
|
||||
}
|
||||
for _, val := range vs.Values {
|
||||
lit, ok := val.(*ast.BasicLit)
|
||||
if !ok || lit.Kind != token.STRING {
|
||||
continue
|
||||
}
|
||||
unquoted, err := strconv.Unquote(lit.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
enumValues[ident.Name] = append(enumValues[ident.Name], unquoted)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnumKey_sortsAndJoins(t *testing.T) {
|
||||
key := EnumKey([]any{"b", "a", "c"})
|
||||
if key != "a|b|c" {
|
||||
t.Fatalf("EnumKey = %q, want %q", key, "a|b|c")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumKey_handlesNonStringValues(t *testing.T) {
|
||||
key := EnumKey([]any{2, 1, 3})
|
||||
if key != "1|2|3" {
|
||||
t.Fatalf("EnumKey = %q, want %q", key, "1|2|3")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_basic(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// Color is a primary color.
|
||||
// swagger:enum Color
|
||||
type Color string
|
||||
|
||||
const (
|
||||
ColorRed Color = "red"
|
||||
ColorGreen Color = "green"
|
||||
ColorBlue Color = "blue"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"red", "green", "blue"})
|
||||
if got[wantKey] != "Color" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// swagger:enum Sttype
|
||||
type StateType string
|
||||
|
||||
const (
|
||||
StateOpen StateType = "open"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for annotation referencing a non-matching type name")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Sttype") {
|
||||
t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_collision(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// swagger:enum Alpha
|
||||
type Alpha string
|
||||
const (
|
||||
AlphaX Alpha = "x"
|
||||
AlphaY Alpha = "y"
|
||||
)
|
||||
|
||||
// swagger:enum Beta
|
||||
type Beta string
|
||||
const (
|
||||
BetaX Beta = "x"
|
||||
BetaY Beta = "y"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected collision error, got nil")
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "Alpha") || !strings.Contains(msg, "Beta") {
|
||||
t.Fatalf("error %q should mention both Alpha and Beta", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected parse error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// swagger:enum Lonely
|
||||
type Lonely string
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for annotation without consts")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Lonely") {
|
||||
t.Fatalf("error %q should mention Lonely", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// Name ordering: `a_consts.go` < `b_type.go`, so readdir returns consts first.
|
||||
// Old single-pass scanner would miss the values; two-pass must not.
|
||||
constsSrc := `package fixture
|
||||
|
||||
const (
|
||||
HueA Hue = "a"
|
||||
HueB Hue = "b"
|
||||
)
|
||||
`
|
||||
typeSrc := `package fixture
|
||||
|
||||
// swagger:enum Hue
|
||||
type Hue string
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "a_consts.go"), []byte(constsSrc), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "b_type.go"), []byte(typeSrc), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"a", "b"})
|
||||
if got[wantKey] != "Hue" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
const (
|
||||
ShadeDark Shade = "dark"
|
||||
ShadeLight Shade = "light"
|
||||
)
|
||||
|
||||
// swagger:enum Shade
|
||||
type Shade string
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"dark", "light"})
|
||||
if got[wantKey] != "Shade" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
type (
|
||||
// swagger:enum Color
|
||||
Color string
|
||||
// swagger:enum Shade
|
||||
Shade string
|
||||
)
|
||||
|
||||
const (
|
||||
ColorRed Color = "red"
|
||||
ColorBlue Color = "blue"
|
||||
)
|
||||
|
||||
const (
|
||||
ShadeDark Shade = "dark"
|
||||
ShadeLight Shade = "light"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
colorKey := EnumKey([]any{"red", "blue"})
|
||||
shadeKey := EnumKey([]any{"dark", "light"})
|
||||
if got[colorKey] != "Color" {
|
||||
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
|
||||
}
|
||||
if got[shadeKey] != "Shade" {
|
||||
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
|
||||
}
|
||||
}
|
||||
+37
-7
@@ -1,22 +1,52 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 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"
|
||||
exit 1
|
||||
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
|
||||
baselines=$(cat "./options/locale_en-US.json" | wc -l)
|
||||
baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1)
|
||||
baselines=$((baselines / 4))
|
||||
for filename in ./options/locale/*.json; do
|
||||
lines=$(cat "$filename" | wc -l)
|
||||
if [ "$lines" -lt "$baselines" ]; then
|
||||
for filename in ./options/locale/*.ini; do
|
||||
lines=$(wc -l "$filename" | cut -d" " -f1)
|
||||
if [ $lines -lt $baselines ]; then
|
||||
echo "Removing $filename: $lines/$baselines"
|
||||
rm "$filename"
|
||||
fi
|
||||
done
|
||||
|
||||
mv ./options/locale_en-US.json ./options/locale/
|
||||
mv ./options/locale_en-US.ini ./options/locale/
|
||||
|
||||
+12
-11
@@ -4,27 +4,25 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newActionsCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
var (
|
||||
// CmdActions represents the available actions sub-commands.
|
||||
CmdActions = &cli.Command{
|
||||
Name: "actions",
|
||||
Usage: "Manage Gitea Actions",
|
||||
Commands: []*cli.Command{
|
||||
newActionsGenerateRunnerTokenCommand(),
|
||||
Subcommands: []*cli.Command{
|
||||
subcmdActionsGenRunnerToken,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newActionsGenerateRunnerTokenCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
subcmdActionsGenRunnerToken = &cli.Command{
|
||||
Name: "generate-runner-token",
|
||||
Usage: "Generate a new token for a runner to use to register with the server",
|
||||
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()
|
||||
|
||||
scope := c.String("scope")
|
||||
|
||||
+42
-48
@@ -15,71 +15,64 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newAdminCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
var (
|
||||
// CmdAdmin represents the available admin sub-command.
|
||||
CmdAdmin = &cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Perform common administrative operations",
|
||||
Commands: []*cli.Command{
|
||||
newUserCommand(),
|
||||
newRepoSyncReleasesCommand(),
|
||||
newRegenerateCommand(),
|
||||
newAuthCommand(),
|
||||
newSendMailCommand(),
|
||||
Subcommands: []*cli.Command{
|
||||
subcmdUser,
|
||||
subcmdRepoSyncReleases,
|
||||
subcmdRegenerate,
|
||||
subcmdAuth,
|
||||
subcmdSendMail,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newRepoSyncReleasesCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
subcmdRepoSyncReleases = &cli.Command{
|
||||
Name: "repo-sync-releases",
|
||||
Usage: "Synchronize repository releases with tags",
|
||||
Action: runRepoSyncReleases,
|
||||
}
|
||||
}
|
||||
|
||||
func newRegenerateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
subcmdRegenerate = &cli.Command{
|
||||
Name: "regenerate",
|
||||
Usage: "Regenerate specific files",
|
||||
Commands: []*cli.Command{
|
||||
newRegenerateHooksCommand(),
|
||||
newRegenerateKeysCommand(),
|
||||
Subcommands: []*cli.Command{
|
||||
microcmdRegenHooks,
|
||||
microcmdRegenKeys,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newAuthCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
subcmdAuth = &cli.Command{
|
||||
Name: "auth",
|
||||
Usage: "Modify external auth providers",
|
||||
Commands: []*cli.Command{
|
||||
microcmdAuthAddOauth(),
|
||||
microcmdAuthUpdateOauth(),
|
||||
microcmdAuthAddLdapBindDn(),
|
||||
microcmdAuthUpdateLdapBindDn(),
|
||||
microcmdAuthAddLdapSimpleAuth(),
|
||||
microcmdAuthUpdateLdapSimpleAuth(),
|
||||
microcmdAuthAddSMTP(),
|
||||
microcmdAuthUpdateSMTP(),
|
||||
newAuthListCommand(),
|
||||
newAuthDeleteCommand(),
|
||||
Subcommands: []*cli.Command{
|
||||
microcmdAuthAddOauth,
|
||||
microcmdAuthUpdateOauth,
|
||||
microcmdAuthAddLdapBindDn,
|
||||
microcmdAuthUpdateLdapBindDn,
|
||||
microcmdAuthAddLdapSimpleAuth,
|
||||
microcmdAuthUpdateLdapSimpleAuth,
|
||||
microcmdAuthAddSMTP,
|
||||
microcmdAuthUpdateSMTP,
|
||||
microcmdAuthList,
|
||||
microcmdAuthDelete,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newSendMailCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
subcmdSendMail = &cli.Command{
|
||||
Name: "sendmail",
|
||||
Usage: "Send a message to all users",
|
||||
Action: runSendMail,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Usage: "a title of a message",
|
||||
Required: true,
|
||||
Name: "title",
|
||||
Usage: `a title of a message`,
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "content",
|
||||
@@ -93,27 +86,28 @@ func newSendMailCommand() *cli.Command {
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func idFlag() *cli.Int64Flag {
|
||||
return &cli.Int64Flag{
|
||||
idFlag = &cli.Int64Flag{
|
||||
Name: "id",
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := git.InitSimple(); err != nil {
|
||||
if err := git.InitSimple(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("Synchronizing repository releases (this may take a while)")
|
||||
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{
|
||||
PageSize: repo_model.RepositoryListDefaultPageSize,
|
||||
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)
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
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)
|
||||
gitRepo.Close()
|
||||
continue
|
||||
@@ -154,7 +148,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||
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)
|
||||
gitRepo.Close()
|
||||
}
|
||||
|
||||
+14
-12
@@ -4,7 +4,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -14,20 +13,17 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
auth_service "code.gitea.io/gitea/services/auth"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newAuthDeleteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
var (
|
||||
microcmdAuthDelete = &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete specific auth source",
|
||||
Flags: []cli.Flag{idFlag()},
|
||||
Flags: []cli.Flag{idFlag},
|
||||
Action: runDeleteAuth,
|
||||
}
|
||||
}
|
||||
|
||||
func newAuthListCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
microcmdAuthList = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List auth sources",
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -91,11 +90,14 @@ func runListAuth(ctx context.Context, c *cli.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDeleteAuth(ctx context.Context, c *cli.Command) error {
|
||||
func runDeleteAuth(c *cli.Context) error {
|
||||
if !c.IsSet("id") {
|
||||
return errors.New("--id flag is missing")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+45
-50
@@ -12,7 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -24,8 +24,8 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func commonLdapCLIFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
var (
|
||||
commonLdapCLIFlags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "Authentication name.",
|
||||
@@ -94,10 +94,6 @@ func commonLdapCLIFlags() []cli.Flag {
|
||||
Name: "public-ssh-key-attribute",
|
||||
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{
|
||||
Name: "skip-local-2fa",
|
||||
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.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ldapBindDnCLIFlags() []cli.Flag {
|
||||
return append(commonLdapCLIFlags(),
|
||||
ldapBindDnCLIFlags = append(commonLdapCLIFlags,
|
||||
&cli.StringFlag{
|
||||
Name: "bind-dn",
|
||||
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",
|
||||
Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
|
||||
})
|
||||
}
|
||||
|
||||
func ldapSimpleAuthCLIFlags() []cli.Flag {
|
||||
return append(commonLdapCLIFlags(),
|
||||
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
|
||||
&cli.StringFlag{
|
||||
Name: "user-dn",
|
||||
Usage: "The user's DN.",
|
||||
})
|
||||
}
|
||||
|
||||
func microcmdAuthAddLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
microcmdAuthAddLdapBindDn = &cli.Command{
|
||||
Name: "add-ldap",
|
||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().addLdapBindDn(ctx, cmd)
|
||||
Action: func(c *cli.Context) error {
|
||||
return newAuthService().addLdapBindDn(c)
|
||||
},
|
||||
Flags: ldapBindDnCLIFlags(),
|
||||
Flags: ldapBindDnCLIFlags,
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
microcmdAuthUpdateLdapBindDn = &cli.Command{
|
||||
Name: "update-ldap",
|
||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().updateLdapBindDn(ctx, cmd)
|
||||
Action: func(c *cli.Context) error {
|
||||
return newAuthService().updateLdapBindDn(c)
|
||||
},
|
||||
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),
|
||||
Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
microcmdAuthAddLdapSimpleAuth = &cli.Command{
|
||||
Name: "add-ldap-simple",
|
||||
Usage: "Add new LDAP (simple auth) authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().addLdapSimpleAuth(ctx, cmd)
|
||||
Action: func(c *cli.Context) error {
|
||||
return newAuthService().addLdapSimpleAuth(c)
|
||||
},
|
||||
Flags: ldapSimpleAuthCLIFlags(),
|
||||
Flags: ldapSimpleAuthCLIFlags,
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
|
||||
Name: "update-ldap-simple",
|
||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().updateLdapSimpleAuth(ctx, cmd)
|
||||
Action: func(c *cli.Context) error {
|
||||
return newAuthService().updateLdapSimpleAuth(c)
|
||||
},
|
||||
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
|
||||
Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// newAuthService creates a service with default functions.
|
||||
func newAuthService() *authService {
|
||||
@@ -228,7 +212,7 @@ func newAuthService() *authService {
|
||||
}
|
||||
|
||||
// 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") {
|
||||
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.
|
||||
func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
|
||||
func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
|
||||
if c.IsSet("name") {
|
||||
config.Name = c.String("name")
|
||||
}
|
||||
@@ -261,7 +245,7 @@ func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
|
||||
if c.IsSet("security-protocol") {
|
||||
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
|
||||
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
|
||||
}
|
||||
@@ -298,9 +282,6 @@ func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
|
||||
if c.IsSet("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") {
|
||||
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.
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
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.
|
||||
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 {
|
||||
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.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
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.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
+40
-35
@@ -8,16 +8,17 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestAddLdapBindDn(t *testing.T) {
|
||||
// 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
|
||||
cases := []struct {
|
||||
@@ -134,7 +135,7 @@ func TestAddLdapBindDn(t *testing.T) {
|
||||
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
|
||||
"--email-attribute", "mail",
|
||||
},
|
||||
errMsg: "unknown security protocol name: zzzzz",
|
||||
errMsg: "Unknown security protocol name: zzzzz",
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
@@ -233,18 +234,17 @@ func TestAddLdapBindDn(t *testing.T) {
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
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
|
||||
app := cli.Command{
|
||||
Flags: microcmdAuthAddLdapBindDn().Flags,
|
||||
Action: service.addLdapBindDn,
|
||||
}
|
||||
app := cli.NewApp()
|
||||
app.Flags = microcmdAuthAddLdapBindDn.Flags
|
||||
app.Action = service.addLdapBindDn
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
err := app.Run(c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
@@ -256,7 +256,9 @@ func TestAddLdapBindDn(t *testing.T) {
|
||||
|
||||
func TestAddLdapSimpleAuth(t *testing.T) {
|
||||
// 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
|
||||
cases := []struct {
|
||||
@@ -346,12 +348,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
|
||||
"--name", "ldap (simple auth) source",
|
||||
"--security-protocol", "zzzzz",
|
||||
"--host", "ldap-server",
|
||||
"--port", "1234",
|
||||
"--port", "123",
|
||||
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
|
||||
"--email-attribute", "mail",
|
||||
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
|
||||
},
|
||||
errMsg: "unknown security protocol name: zzzzz",
|
||||
errMsg: "Unknown security protocol name: zzzzz",
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
@@ -463,18 +465,17 @@ func TestAddLdapSimpleAuth(t *testing.T) {
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
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
|
||||
app := &cli.Command{
|
||||
Flags: microcmdAuthAddLdapSimpleAuth().Flags,
|
||||
Action: service.addLdapSimpleAuth,
|
||||
}
|
||||
app := cli.NewApp()
|
||||
app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
|
||||
app.Action = service.addLdapSimpleAuth
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
err := app.Run(c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
@@ -486,7 +487,9 @@ func TestAddLdapSimpleAuth(t *testing.T) {
|
||||
|
||||
func TestUpdateLdapBindDn(t *testing.T) {
|
||||
// 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
|
||||
cases := []struct {
|
||||
@@ -861,7 +864,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
||||
"--id", "1",
|
||||
"--security-protocol", "xxxxx",
|
||||
},
|
||||
errMsg: "unknown security protocol name: xxxxx",
|
||||
errMsg: "Unknown security protocol name: xxxxx",
|
||||
},
|
||||
// case 22
|
||||
{
|
||||
@@ -880,7 +883,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
||||
Type: auth.OAuth2,
|
||||
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
|
||||
{
|
||||
@@ -944,12 +947,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{
|
||||
Flags: microcmdAuthUpdateLdapBindDn().Flags,
|
||||
Action: service.updateLdapBindDn,
|
||||
}
|
||||
app := cli.NewApp()
|
||||
app.Flags = microcmdAuthUpdateLdapBindDn.Flags
|
||||
app.Action = service.updateLdapBindDn
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
err := app.Run(c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
@@ -961,7 +964,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
||||
|
||||
func TestUpdateLdapSimpleAuth(t *testing.T) {
|
||||
// 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
|
||||
cases := []struct {
|
||||
@@ -1252,7 +1257,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
||||
"--id", "1",
|
||||
"--security-protocol", "xxxxx",
|
||||
},
|
||||
errMsg: "unknown security protocol name: xxxxx",
|
||||
errMsg: "Unknown security protocol name: xxxxx",
|
||||
},
|
||||
// case 18
|
||||
{
|
||||
@@ -1271,7 +1276,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
||||
Type: auth.PAM,
|
||||
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
|
||||
{
|
||||
@@ -1332,12 +1337,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{
|
||||
Flags: microcmdAuthUpdateLdapSimpleAuth().Flags,
|
||||
Action: service.updateLdapSimpleAuth,
|
||||
}
|
||||
app := cli.NewApp()
|
||||
app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
|
||||
app.Action = service.updateLdapSimpleAuth
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
err := app.Run(c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
|
||||
+28
-50
@@ -4,7 +4,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@@ -13,11 +12,11 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func oauthCLIFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
var (
|
||||
oauthCLIFlags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "",
|
||||
@@ -87,14 +86,6 @@ func oauthCLIFlags() []cli.Flag {
|
||||
Value: nil,
|
||||
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{
|
||||
Name: "required-claim-name",
|
||||
Value: "",
|
||||
@@ -130,34 +121,23 @@ func oauthCLIFlags() []cli.Flag {
|
||||
Usage: "Activate automatic team membership removal depending on groups",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthAddOauth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-oauth",
|
||||
Usage: "Add new Oauth authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().runAddOauth(ctx, cmd)
|
||||
},
|
||||
Flags: oauthCLIFlags(),
|
||||
microcmdAuthAddOauth = &cli.Command{
|
||||
Name: "add-oauth",
|
||||
Usage: "Add new Oauth authentication source",
|
||||
Action: runAddOauth,
|
||||
Flags: oauthCLIFlags,
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthUpdateOauth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-oauth",
|
||||
Usage: "Update existing Oauth authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().runUpdateOauth(ctx, cmd)
|
||||
},
|
||||
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
|
||||
Name: "id",
|
||||
Usage: "ID of authentication source",
|
||||
}}, oauthCLIFlags()[1:]...)...),
|
||||
microcmdAuthUpdateOauth = &cli.Command{
|
||||
Name: "update-oauth",
|
||||
Usage: "Update existing Oauth authentication source",
|
||||
Action: runUpdateOauth,
|
||||
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
func parseOAuth2Config(c *cli.Command) *oauth2.Source {
|
||||
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
||||
var customURLMapping *oauth2.CustomURLMapping
|
||||
if c.IsSet("use-custom-urls") {
|
||||
customURLMapping = &oauth2.CustomURLMapping{
|
||||
@@ -185,13 +165,14 @@ func parseOAuth2Config(c *cli.Command) *oauth2.Source {
|
||||
RestrictedGroup: c.String("restricted-group"),
|
||||
GroupTeamMap: c.String("group-team-map"),
|
||||
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 {
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
func runAddOauth(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
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,
|
||||
Name: c.String("name"),
|
||||
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") {
|
||||
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
|
||||
}
|
||||
|
||||
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
||||
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -278,12 +262,6 @@ func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error
|
||||
if c.IsSet("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
|
||||
customURLMapping := &oauth2.CustomURLMapping{}
|
||||
@@ -318,5 +296,5 @@ func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error
|
||||
oAuth2Config.CustomURLMapping = customURLMapping
|
||||
source.Cfg = oAuth2Config
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
@@ -12,11 +11,11 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func smtpCLIFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
var (
|
||||
smtpCLIFlags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "",
|
||||
@@ -68,34 +67,23 @@ func smtpCLIFlags() []cli.Flag {
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthUpdateSMTP() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-smtp",
|
||||
Usage: "Update existing SMTP authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().runUpdateSMTP(ctx, cmd)
|
||||
},
|
||||
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
|
||||
Name: "id",
|
||||
Usage: "ID of authentication source",
|
||||
}}, smtpCLIFlags()[1:]...)...),
|
||||
microcmdAuthAddSMTP = &cli.Command{
|
||||
Name: "add-smtp",
|
||||
Usage: "Add new SMTP authentication source",
|
||||
Action: runAddSMTP,
|
||||
Flags: smtpCLIFlags,
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthAddSMTP() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-smtp",
|
||||
Usage: "Add new SMTP authentication source",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
return newAuthService().runAddSMTP(ctx, cmd)
|
||||
},
|
||||
Flags: smtpCLIFlags(),
|
||||
microcmdAuthUpdateSMTP = &cli.Command{
|
||||
Name: "update-smtp",
|
||||
Usage: "Update existing SMTP authentication source",
|
||||
Action: runUpdateSMTP,
|
||||
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
|
||||
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
||||
if c.IsSet("auth-type") {
|
||||
conf.Auth = c.String("auth-type")
|
||||
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
|
||||
@@ -128,8 +116,11 @@ func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
func runAddSMTP(c *cli.Context) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -157,7 +148,7 @@ func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
|
||||
smtpConfig.Auth = "PLAIN"
|
||||
}
|
||||
|
||||
return a.createAuthSource(ctx, &auth_model.Source{
|
||||
return auth_model.CreateSource(ctx, &auth_model.Source{
|
||||
Type: auth_model.SMTP,
|
||||
Name: c.String("name"),
|
||||
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") {
|
||||
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
|
||||
}
|
||||
|
||||
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
|
||||
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -196,5 +190,5 @@ func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error {
|
||||
|
||||
source.Cfg = smtpConfig
|
||||
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
|
||||
return a.updateAuthSource(ctx, source)
|
||||
return auth_model.UpdateSource(ctx, source)
|
||||
}
|
||||
+13
-11
@@ -4,39 +4,41 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newRegenerateHooksCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
var (
|
||||
microcmdRegenHooks = &cli.Command{
|
||||
Name: "hooks",
|
||||
Usage: "Regenerate git-hooks",
|
||||
Action: runRegenerateHooks,
|
||||
}
|
||||
}
|
||||
|
||||
func newRegenerateKeysCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
microcmdRegenKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "Regenerate authorized_keys file",
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
+12
-14
@@ -4,20 +4,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newUserCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "user",
|
||||
Usage: "Modify users",
|
||||
Commands: []*cli.Command{
|
||||
microcmdUserCreate(),
|
||||
newUserListCommand(),
|
||||
microcmdUserChangePassword(),
|
||||
microcmdUserDelete(),
|
||||
newUserGenerateAccessTokenCommand(),
|
||||
microcmdUserMustChangePassword(),
|
||||
},
|
||||
}
|
||||
var subcmdUser = &cli.Command{
|
||||
Name: "user",
|
||||
Usage: "Modify users",
|
||||
Subcommands: []*cli.Command{
|
||||
microcmdUserCreate,
|
||||
microcmdUserList,
|
||||
microcmdUserChangePassword,
|
||||
microcmdUserDelete,
|
||||
microcmdUserGenerateAccessToken,
|
||||
microcmdUserMustChangePassword,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@@ -14,41 +13,44 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func microcmdUserChangePassword() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
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,
|
||||
},
|
||||
var microcmdUserChangePassword = &cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
Value: "",
|
||||
Usage: "The user to change password for",
|
||||
},
|
||||
}
|
||||
&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 {
|
||||
if !setting.IsInTesting {
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
func runChangePassword(c *cli.Context) error {
|
||||
if err := argsSet(c, "username", "password"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"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) {
|
||||
cmd := microcmdUserChangePassword()
|
||||
cmd.Writer, cmd.ErrWriter = io.Discard, io.Discard
|
||||
err := cmd.Run(ctx, tc.args)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.expectedErr)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
+83
-82
@@ -16,95 +16,87 @@ import (
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func microcmdUserCreate() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a new user in database",
|
||||
Action: runCreateUser,
|
||||
MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{
|
||||
{
|
||||
Flags: [][]cli.Flag{
|
||||
{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "Username. DEPRECATED: use username instead",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "Username",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: true,
|
||||
},
|
||||
var microcmdUserCreate = &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a new user in database",
|
||||
Action: runCreateUser,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "Username. DEPRECATED: use username instead",
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&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",
|
||||
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: "username",
|
||||
Usage: "Username",
|
||||
},
|
||||
}
|
||||
&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
|
||||
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
|
||||
setting.LoadSettings()
|
||||
|
||||
if err := argsSet(c, "email"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userTypes := map[string]user_model.UserType{
|
||||
"individual": user_model.UserTypeIndividual,
|
||||
"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")
|
||||
}
|
||||
}
|
||||
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") {
|
||||
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")
|
||||
} else {
|
||||
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 {
|
||||
// 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
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = installSignals()
|
||||
defer cancel()
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -151,7 +153,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// codeql[disable-next-line=go/clear-text-logging]
|
||||
fmt.Printf("generated random password is '%s'\n", password)
|
||||
} else if userType == user_model.UserTypeIndividual {
|
||||
return errors.New("must set either password or random-password flag")
|
||||
|
||||
@@ -18,10 +18,12 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminUserCreate(t *testing.T) {
|
||||
app := NewMainApp(AppVersion{})
|
||||
|
||||
reset := 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{}))
|
||||
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
||||
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
|
||||
require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
|
||||
}
|
||||
|
||||
t.Run("MustChangePassword", func(t *testing.T) {
|
||||
@@ -29,9 +31,8 @@ func TestAdminUserCreate(t *testing.T) {
|
||||
IsAdmin bool
|
||||
MustChangePassword bool
|
||||
}
|
||||
|
||||
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})
|
||||
return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword}
|
||||
}
|
||||
@@ -50,7 +51,7 @@ func TestAdminUserCreate(t *testing.T) {
|
||||
})
|
||||
|
||||
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) {
|
||||
|
||||
+32
-35
@@ -4,56 +4,53 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func microcmdUserDelete() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete specific user by id, name or email",
|
||||
Flags: []cli.Flag{
|
||||
&cli.Int64Flag{
|
||||
Name: "id",
|
||||
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",
|
||||
},
|
||||
var microcmdUserDelete = &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete specific user by id, name or email",
|
||||
Flags: []cli.Flag{
|
||||
&cli.Int64Flag{
|
||||
Name: "id",
|
||||
Usage: "ID of user of the user to delete",
|
||||
},
|
||||
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") {
|
||||
return errors.New("You must provide the id, username or email of a user to delete")
|
||||
}
|
||||
|
||||
if !setting.IsInTesting {
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := storage.Init(); err != nil {
|
||||
@@ -73,11 +70,11 @@ func runDeleteUser(ctx context.Context, c *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
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") {
|
||||
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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newUserGenerateAccessTokenCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "generate-access-token",
|
||||
Usage: "Generate an access token for a specific user",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
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"`,
|
||||
},
|
||||
var microcmdUserGenerateAccessToken = &cli.Command{
|
||||
Name: "generate-access-token",
|
||||
Usage: "Generate an access token for a specific user",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "Username",
|
||||
},
|
||||
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") {
|
||||
return errors.New("you must provide a username to generate a token for")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+14
-14
@@ -4,31 +4,31 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newUserListCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List users",
|
||||
Action: runListUsers,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "admin",
|
||||
Usage: "List only admin users",
|
||||
},
|
||||
var microcmdUserList = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List users",
|
||||
Action: runListUsers,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "admin",
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,41 +4,40 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
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 {
|
||||
return &cli.Command{
|
||||
Name: "must-change-password",
|
||||
Usage: "Set the must change password flag for the provided users or all users",
|
||||
Action: runMustChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"A"},
|
||||
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",
|
||||
},
|
||||
var microcmdUserMustChangePassword = &cli.Command{
|
||||
Name: "must-change-password",
|
||||
Usage: "Set the must change password flag for the provided users or all users",
|
||||
Action: runMustChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"A"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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") {
|
||||
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")
|
||||
exclude := c.StringSlice("exclude")
|
||||
|
||||
if !setting.IsInTesting {
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// codeql[disable-next-line=go/clear-text-logging]
|
||||
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
|
||||
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)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user