feat: vendor gitea 1.16.2

This commit is contained in:
2026-06-02 08:36:01 +00:00
parent 247cd60f69
commit 54798b4615
2145 changed files with 162965 additions and 126447 deletions
+17 -8
View File
@@ -1,6 +1,6 @@
{ {
lib, lib,
buildGoModule, buildGo126Module,
makeWrapper, makeWrapper,
git, git,
bash, bash,
@@ -10,7 +10,7 @@
openssh, openssh,
fetchPnpmDeps, fetchPnpmDeps,
pnpmConfigHook, pnpmConfigHook,
pnpm, pnpm_10,
stdenv, stdenv,
sqliteSupport ? true, sqliteSupport ? true,
nixosTests, nixosTests,
@@ -18,8 +18,9 @@
let let
pname = "gitea"; pname = "gitea";
version = "1.25.4"; version = "1.26.2";
src = ./source; src = ./source;
pnpm = pnpm_10;
frontend = stdenv.mkDerivation { frontend = stdenv.mkDerivation {
pname = "gitea-frontend"; pname = "gitea-frontend";
@@ -28,8 +29,9 @@ let
pnpmDeps = fetchPnpmDeps { pnpmDeps = fetchPnpmDeps {
pname = "gitea-frontend"; pname = "gitea-frontend";
inherit version src; inherit version src;
fetcherVersion = 2; inherit pnpm;
hash = "sha256-0p7P68BvO3hv0utUbnPpHSpGLlV7F9HHmOITvJAb/ww="; fetcherVersion = 3;
hash = "sha256-Qo0DLuZv+2GVLsBfCv/6CC9E/qhSE4HwV4StQL4HX4Y=";
}; };
nativeBuildInputs = [ nativeBuildInputs = [
@@ -48,11 +50,11 @@ let
''; '';
}; };
in in
buildGoModule rec { buildGo126Module rec {
inherit pname version src; inherit pname version src;
proxyVendor = true; proxyVendor = true;
vendorHash = "sha256-y7HurJg+/V1cn8iKDXepk/ie/iNgiJXsQbDi1dhgark="; vendorHash = "sha256-7+M1n8RSgB3gZ/2na4RF9kYOf90H0bnsJZMDKpgAy64=";
outputs = [ outputs = [
"out" "out"
@@ -61,10 +63,17 @@ buildGoModule rec {
patches = [ ./static-root-path.patch ]; patches = [ ./static-root-path.patch ];
overrideModAttrs = _: { postPatch = null; }; overrideModAttrs = _: {
postPatch = ''
substituteInPlace go.mod \
--replace-fail "go 1.26.3" "go 1.26"
'';
};
postPatch = '' postPatch = ''
substituteInPlace modules/setting/server.go --subst-var data substituteInPlace modules/setting/server.go --subst-var data
substituteInPlace go.mod \
--replace-fail "go 1.26.3" "go 1.26"
''; '';
subPackages = [ "." ]; subPackages = [ "." ];
+1 -1
View File
@@ -4,7 +4,7 @@ tmp_dir = ".air"
[build] [build]
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
cmd = "make --no-print-directory backend" cmd = "make --no-print-directory backend"
bin = "gitea" entrypoint = ["./gitea"]
delay = 2000 delay = 2000
include_ext = ["go", "tmpl"] include_ext = ["go", "tmpl"]
include_file = ["main.go"] include_file = ["main.go"]
@@ -13,14 +13,13 @@
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {}, "ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}, "ghcr.io/jsburckhardt/devcontainer-features/uv:1": {},
"ghcr.io/devcontainers/features/python:1": { "ghcr.io/devcontainers/features/python:1": {
"version": "3.13" "version": "3.14"
}, },
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
}, },
"customizations": { "customizations": {
"vscode": { "vscode": {
"settings": {}, "settings": {},
// same extensions as Gitpod, should match /.gitpod.yml
"extensions": [ "extensions": [
"editorconfig.editorconfig", "editorconfig.editorconfig",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
+3
View File
@@ -74,6 +74,9 @@ cpu.out
/VERSION /VERSION
/.air /.air
/.go-licenses /.go-licenses
/Dockerfile
/Dockerfile.rootless
/.venv
# Files and folders that were previously generated # Files and folders that were previously generated
/public/assets/img/webpack /public/assets/img/webpack
+1
View File
@@ -8,3 +8,4 @@
/vendor/** -text -eol linguist-vendored /vendor/** -text -eol linguist-vendored
/web_src/js/vendor/** -text -eol linguist-vendored /web_src/js/vendor/** -text -eol linguist-vendored
Dockerfile.* linguist-language=Dockerfile Dockerfile.* linguist-language=Dockerfile
Makefile.* linguist-language=Makefile
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
labels: [modifies/dependencies]
directory: /
schedule:
interval: daily
cooldown:
default-days: 5
+3 -5
View File
@@ -43,15 +43,13 @@ modifies/internal:
- ".editorconfig" - ".editorconfig"
- ".eslintrc.cjs" - ".eslintrc.cjs"
- ".golangci.yml" - ".golangci.yml"
- ".gitpod.yml"
- ".markdownlint.yaml" - ".markdownlint.yaml"
- ".spectral.yaml" - ".spectral.yaml"
- "stylelint.config.js" - "stylelint.config.*"
- ".yamllint.yaml" - ".yamllint.yaml"
- ".github/**" - ".github/**"
- ".gitea/**" - ".gitea/**"
- ".devcontainer/**" - ".devcontainer/**"
- "build.go"
- "build/**" - "build/**"
- "contrib/**" - "contrib/**"
@@ -85,9 +83,9 @@ docs-update-needed:
topic/code-linting: topic/code-linting:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- ".eslintrc.cjs"
- ".golangci.yml" - ".golangci.yml"
- ".markdownlint.yaml" - ".markdownlint.yaml"
- ".spectral.yaml" - ".spectral.yaml"
- ".yamllint.yaml" - ".yamllint.yaml"
- "stylelint.config.js" - "eslint*.config.*"
- "stylelint.config.*"
@@ -0,0 +1,22 @@
name: cron-flake-updater
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # runs weekly on Sunday at 00:00
jobs:
nix-flake-update:
permissions:
contents: write
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: DeterminateSystems/determinate-nix-action@v3
- uses: DeterminateSystems/update-flake-lock@main
with:
pr-title: "Update Nix flake"
pr-labels: |
dependencies
+4 -2
View File
@@ -9,8 +9,10 @@ jobs:
cron-licenses: cron-licenses:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea' if: github.repository == 'go-gitea/gitea'
permissions:
contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -18,7 +20,7 @@ jobs:
- run: make generate-gitignore - run: make generate-gitignore
timeout-minutes: 40 timeout-minutes: 40
- name: push translations to repo - name: push translations to repo
uses: appleboy/git-push-action@v0.0.3 uses: appleboy/git-push-action@v1.2.0
with: with:
author_email: "teabot@gitea.io" author_email: "teabot@gitea.io"
author_name: GiteaBot author_name: GiteaBot
@@ -9,9 +9,11 @@ jobs:
crowdin-pull: crowdin-pull:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea' if: github.repository == 'go-gitea/gitea'
permissions:
contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: crowdin/github-action@v1 - uses: crowdin/github-action@v2
with: with:
upload_sources: true upload_sources: true
upload_translations: false upload_translations: false
@@ -27,7 +29,7 @@ jobs:
- name: update locales - name: update locales
run: ./build/update-locales.sh run: ./build/update-locales.sh
- name: push translations to repo - name: push translations to repo
uses: appleboy/git-push-action@v0.0.3 uses: appleboy/git-push-action@v1.2.0
with: with:
author_email: "teabot@gitea.io" author_email: "teabot@gitea.io"
author_name: GiteaBot author_name: GiteaBot
+12 -3
View File
@@ -19,11 +19,15 @@ on:
value: ${{ jobs.detect.outputs.swagger }} value: ${{ jobs.detect.outputs.swagger }}
yaml: yaml:
value: ${{ jobs.detect.outputs.yaml }} value: ${{ jobs.detect.outputs.yaml }}
json:
value: ${{ jobs.detect.outputs.json }}
jobs: jobs:
detect: detect:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 3 timeout-minutes: 3
permissions:
contents: read
outputs: outputs:
backend: ${{ steps.changes.outputs.backend }} backend: ${{ steps.changes.outputs.backend }}
frontend: ${{ steps.changes.outputs.frontend }} frontend: ${{ steps.changes.outputs.frontend }}
@@ -33,9 +37,10 @@ jobs:
docker: ${{ steps.changes.outputs.docker }} docker: ${{ steps.changes.outputs.docker }}
swagger: ${{ steps.changes.outputs.swagger }} swagger: ${{ steps.changes.outputs.swagger }}
yaml: ${{ steps.changes.outputs.yaml }} yaml: ${{ steps.changes.outputs.yaml }}
json: ${{ steps.changes.outputs.json }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: dorny/paths-filter@v3 - uses: dorny/paths-filter@v4
id: changes id: changes
with: with:
filters: | filters: |
@@ -48,7 +53,7 @@ jobs:
- "Makefile" - "Makefile"
- ".golangci.yml" - ".golangci.yml"
- ".editorconfig" - ".editorconfig"
- "options/locale/locale_en-US.ini" - "options/locale/locale_en-US.json"
frontend: frontend:
- "*.js" - "*.js"
@@ -80,6 +85,7 @@ jobs:
- "uv.lock" - "uv.lock"
docker: docker:
- ".github/workflows/pull-docker-dryrun.yml"
- "Dockerfile" - "Dockerfile"
- "Dockerfile.rootless" - "Dockerfile.rootless"
- "docker/**" - "docker/**"
@@ -98,3 +104,6 @@ jobs:
- "**/*.yaml" - "**/*.yaml"
- ".yamllint.yaml" - ".yamllint.yaml"
- "pyproject.toml" - "pyproject.toml"
json:
- "**/*.json"
+73 -24
View File
@@ -10,13 +10,17 @@ concurrency:
jobs: jobs:
files-changed: files-changed:
uses: ./.github/workflows/files-changed.yml uses: ./.github/workflows/files-changed.yml
permissions:
contents: read
lint-backend: lint-backend:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -30,14 +34,18 @@ jobs:
if: needs.files-changed.outputs.templates == 'true' if: needs.files-changed.outputs.templates == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v6 - uses: astral-sh/setup-uv@v8.0.0
- run: uv python install 3.12 - run: uv python install 3.14
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-py - run: make deps-py
- run: make deps-frontend - run: make deps-frontend
- run: make lint-templates - run: make lint-templates
@@ -46,23 +54,44 @@ jobs:
if: needs.files-changed.outputs.yaml == 'true' if: needs.files-changed.outputs.yaml == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v6 - uses: astral-sh/setup-uv@v8.0.0
- run: uv python install 3.12 - run: uv python install 3.14
- run: make deps-py - run: make deps-py
- run: make lint-yaml - run: make lint-yaml
lint-json:
if: needs.files-changed.outputs.json == 'true'
needs: files-changed
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5
with:
node-version: 24
- run: make deps-frontend
- run: make lint-json
lint-swagger: lint-swagger:
if: needs.files-changed.outputs.swagger == 'true' if: needs.files-changed.outputs.swagger == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend - run: make deps-frontend
- run: make lint-swagger - run: make lint-swagger
@@ -70,8 +99,10 @@ jobs:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -82,8 +113,10 @@ jobs:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -99,8 +132,10 @@ jobs:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -114,8 +149,10 @@ jobs:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -127,12 +164,16 @@ jobs:
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend - run: make deps-frontend
- run: make lint-frontend - run: make lint-frontend
- run: make checks-frontend - run: make checks-frontend
@@ -143,8 +184,10 @@ jobs:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -175,12 +218,16 @@ jobs:
if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend - run: make deps-frontend
- run: make lint-md - run: make lint-md
@@ -188,8 +235,10 @@ jobs:
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
+20 -12
View File
@@ -10,11 +10,15 @@ concurrency:
jobs: jobs:
files-changed: files-changed:
uses: ./.github/workflows/files-changed.yml uses: ./.github/workflows/files-changed.yml
permissions:
contents: read
test-pgsql: test-pgsql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
services: services:
pgsql: pgsql:
image: postgres:14 image: postgres:14
@@ -38,7 +42,7 @@ jobs:
ports: ports:
- "9000:9000" - "9000:9000"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -59,37 +63,39 @@ jobs:
RACE_ENABLED: true RACE_ENABLED: true
TEST_TAGS: gogit TEST_TAGS: gogit
TEST_LDAP: 1 TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
test-sqlite: test-sqlite:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- run: make deps-backend - run: make deps-backend
- run: make backend - run: GOEXPERIMENT='' make backend
env: env:
TAGS: bindata gogit sqlite sqlite_unlock_notify TAGS: bindata gogit sqlite sqlite_unlock_notify
- name: run migration tests - name: run migration tests
run: make test-sqlite-migration run: make test-sqlite-migration
- name: run tests - name: run tests
run: make test-sqlite run: GOEXPERIMENT='' make test-sqlite
timeout-minutes: 50 timeout-minutes: 50
env: env:
TAGS: bindata gogit sqlite sqlite_unlock_notify TAGS: bindata gogit sqlite sqlite_unlock_notify
RACE_ENABLED: true RACE_ENABLED: true
TEST_TAGS: gogit sqlite sqlite_unlock_notify TEST_TAGS: gogit sqlite sqlite_unlock_notify
USE_REPO_TEST_DIR: 1
test-unit: test-unit:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
services: services:
elasticsearch: elasticsearch:
image: elasticsearch:7.5.0 image: elasticsearch:7.5.0
@@ -124,7 +130,7 @@ jobs:
ports: ports:
- 10000:10000 - 10000:10000
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -142,7 +148,7 @@ jobs:
RACE_ENABLED: true RACE_ENABLED: true
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }} GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
- name: unit-tests-gogit - name: unit-tests-gogit
run: make unit-test-coverage test-check run: GOEXPERIMENT='' make unit-test-coverage test-check
env: env:
TAGS: bindata gogit TAGS: bindata gogit
RACE_ENABLED: true RACE_ENABLED: true
@@ -152,6 +158,8 @@ jobs:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
services: services:
mysql: mysql:
# the bitnami mysql image has more options than the official one, it's easier to customize # the bitnami mysql image has more options than the official one, it's easier to customize
@@ -177,7 +185,7 @@ jobs:
- "587:587" - "587:587"
- "993:993" - "993:993"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -196,13 +204,14 @@ jobs:
env: env:
TAGS: bindata TAGS: bindata
RACE_ENABLED: true RACE_ENABLED: true
USE_REPO_TEST_DIR: 1
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200" TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
test-mssql: test-mssql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
services: services:
mssql: mssql:
image: mcr.microsoft.com/mssql/server:2019-latest image: mcr.microsoft.com/mssql/server:2019-latest
@@ -217,7 +226,7 @@ jobs:
ports: ports:
- 10000:10000 - 10000:10000
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
@@ -234,4 +243,3 @@ jobs:
timeout-minutes: 50 timeout-minutes: 50
env: env:
TAGS: bindata TAGS: bindata
USE_REPO_TEST_DIR: 1
+19 -14
View File
@@ -10,26 +10,31 @@ concurrency:
jobs: jobs:
files-changed: files-changed:
uses: ./.github/workflows/files-changed.yml uses: ./.github/workflows/files-changed.yml
permissions:
contents: read
regular: container:
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.docker == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: docker/setup-buildx-action@v3 - uses: actions/checkout@v6
- uses: docker/build-push-action@v5 - uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- name: Build regular container image
uses: docker/build-push-action@v7
with: with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: false push: false
tags: gitea/gitea:linux-amd64 cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
- name: Build rootless container image
rootless: uses: docker/build-push-action@v7
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with: with:
context: .
push: false push: false
platforms: linux/amd64,linux/arm64,linux/riscv64
file: Dockerfile.rootless file: Dockerfile.rootless
tags: gitea/gitea:linux-amd64 cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
+19 -11
View File
@@ -10,26 +10,34 @@ concurrency:
jobs: jobs:
files-changed: files-changed:
uses: ./.github/workflows/files-changed.yml uses: ./.github/workflows/files-changed.yml
permissions:
contents: read
test-e2e: test-e2e:
# 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'
# if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
if: false
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
- run: make deps-frontend frontend deps-backend cache: pnpm
- run: pnpm exec playwright install --with-deps cache-dependency-path: pnpm-lock.yaml
- run: make test-e2e-sqlite - run: make deps-frontend
timeout-minutes: 40 - run: make frontend
- run: make deps-backend
- run: make gitea-e2e
- run: make playwright
- run: make test-e2e
timeout-minutes: 10
env: env:
USE_REPO_TEST_DIR: 1 FORCE_COLOR: 1
GITEA_TEST_E2E_DEBUG: 1
+1 -1
View File
@@ -15,6 +15,6 @@ jobs:
contents: read contents: read
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/labeler@v5 - uses: actions/labeler@v6
with: with:
sync-labels: true sync-labels: true
+52 -60
View File
@@ -11,8 +11,10 @@ concurrency:
jobs: jobs:
nightly-binary: nightly-binary:
runs-on: namespace-profile-gitea-release-binary runs-on: namespace-profile-gitea-release-binary
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
@@ -20,10 +22,12 @@ jobs:
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release
@@ -31,7 +35,7 @@ jobs:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
- name: import gpg key - name: import gpg key
id: import_gpg id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6 uses: crazy-max/ghaction-import-gpg@v7
with: with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }} gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
@@ -48,7 +52,7 @@ jobs:
echo "Cleaned name is ${REF_NAME}" echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: configure aws - name: configure aws
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v6
with: with:
aws-region: ${{ secrets.AWS_REGION }} aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
@@ -56,88 +60,76 @@ jobs:
- name: upload binaries to s3 - name: upload binaries to s3
run: | run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
nightly-docker-rootful:
nightly-container:
runs-on: namespace-profile-gitea-release-docker runs-on: namespace-profile-gitea-release-docker
permissions: permissions:
contents: read
packages: write # to publish to ghcr.io packages: write # to publish to ghcr.io
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6 - uses: docker/setup-qemu-action@v4
with: - uses: docker/setup-buildx-action@v4
go-version-file: go.mod
check-latest: true
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Get cleaned branch name - name: Get cleaned branch name
id: clean_name id: clean_name
run: | run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- uses: docker/metadata-action@v6
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
tags: |
type=raw,value=${{ steps.clean_name.outputs.branch }}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- uses: docker/metadata-action@v6
id: meta_rootless
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
suffix=-rootless
tags: |
type=raw,value=${{ steps.clean_name.outputs.branch }}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT - name: Login to GHCR using PAT
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules - name: build regular docker image
run: make vendor uses: docker/build-push-action@v7
- name: build rootful docker image
uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/riscv64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
tags: |- tags: ${{ steps.meta.outputs.tags }}
gitea/gitea:${{ steps.clean_name.outputs.branch }} annotations: ${{ steps.meta.outputs.annotations }}
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }} cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
nightly-docker-rootless: cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
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@v6
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: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules
run: make vendor
- name: build rootless docker image - name: build rootless docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
file: Dockerfile.rootless file: Dockerfile.rootless
tags: |- tags: ${{ steps.meta_rootless.outputs.tags }}
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless annotations: ${{ steps.meta_rootless.outputs.annotations }}
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless 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
+37 -49
View File
@@ -12,8 +12,10 @@ concurrency:
jobs: jobs:
binary: binary:
runs-on: namespace-profile-gitea-release-binary runs-on: namespace-profile-gitea-release-binary
permissions:
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
@@ -21,10 +23,12 @@ jobs:
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release
@@ -32,7 +36,7 @@ jobs:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
- name: import gpg key - name: import gpg key
id: import_gpg id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6 uses: crazy-max/ghaction-import-gpg@v7
with: with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }} gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
@@ -49,7 +53,7 @@ jobs:
echo "Cleaned name is ${REF_NAME}" echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws - name: configure aws
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v6
with: with:
aws-region: ${{ secrets.AWS_REGION }} aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
@@ -58,7 +62,7 @@ jobs:
run: | run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
- name: Install GH CLI - name: Install GH CLI
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0 uses: dev-hanz-ops/install-gh-cli-action@v0.2.1
with: with:
gh-cli-version: 2.39.1 gh-cli-version: 2.39.1
- name: create github release - name: create github release
@@ -66,18 +70,20 @@ jobs:
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/* gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
container:
runs-on: namespace-profile-gitea-release-docker runs-on: namespace-profile-gitea-release-docker
permissions: permissions:
contents: read
packages: write # to publish to ghcr.io packages: write # to publish to ghcr.io
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3 - uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v4
- uses: docker/metadata-action@v5 - uses: docker/metadata-action@v6
id: meta id: meta
with: with:
images: |- images: |-
@@ -88,38 +94,10 @@ jobs:
# 1.2.3-rc0 # 1.2.3-rc0
tags: | tags: |
type=semver,pattern={{version}} type=semver,pattern={{version}}
- name: Login to Docker Hub annotations: |
uses: docker/login-action@v3 org.opencontainers.image.authors="maintainers@gitea.io"
with: - uses: docker/metadata-action@v6
username: ${{ secrets.DOCKERHUB_USERNAME }} id: meta_rootless
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker-rootless:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with: with:
images: |- images: |-
gitea/gitea gitea/gitea
@@ -131,23 +109,33 @@ jobs:
# 1.2.3-rc0 # 1.2.3-rc0
tags: | tags: |
type=semver,pattern={{version}} type=semver,pattern={{version}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT - name: Login to GHCR using PAT
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image - name: build regular container image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: build rootless container image
uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/riscv64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
file: Dockerfile.rootless file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta_rootless.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} annotations: ${{ steps.meta_rootless.outputs.annotations }}
@@ -15,9 +15,10 @@ jobs:
binary: binary:
runs-on: namespace-profile-gitea-release-binary runs-on: namespace-profile-gitea-release-binary
permissions: permissions:
contents: read
packages: write # to publish to ghcr.io packages: write # to publish to ghcr.io
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
@@ -25,10 +26,12 @@ jobs:
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release
@@ -36,7 +39,7 @@ jobs:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
- name: import gpg key - name: import gpg key
id: import_gpg id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6 uses: crazy-max/ghaction-import-gpg@v7
with: with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }} gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
@@ -53,7 +56,7 @@ jobs:
echo "Cleaned name is ${REF_NAME}" echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws - name: configure aws
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v6
with: with:
aws-region: ${{ secrets.AWS_REGION }} aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
@@ -62,7 +65,7 @@ jobs:
run: | run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
- name: Install GH CLI - name: Install GH CLI
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0 uses: dev-hanz-ops/install-gh-cli-action@v0.2.1
with: with:
gh-cli-version: 2.39.1 gh-cli-version: 2.39.1
- name: create github release - name: create github release
@@ -70,18 +73,20 @@ jobs:
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/* gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
container:
runs-on: namespace-profile-gitea-release-docker runs-on: namespace-profile-gitea-release-docker
permissions: permissions:
contents: read
packages: write # to publish to ghcr.io packages: write # to publish to ghcr.io
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3 - uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v4
- uses: docker/metadata-action@v5 - uses: docker/metadata-action@v6
id: meta id: meta
with: with:
images: |- images: |-
@@ -96,36 +101,10 @@ jobs:
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
- name: Login to Docker Hub annotations: |
uses: docker/login-action@v3 org.opencontainers.image.authors="maintainers@gitea.io"
with: - uses: docker/metadata-action@v6
username: ${{ secrets.DOCKERHUB_USERNAME }} id: meta_rootless
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker-rootless:
runs-on: namespace-profile-gitea-release-docker
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with: with:
images: |- images: |-
gitea/gitea gitea/gitea
@@ -142,23 +121,33 @@ jobs:
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT - name: Login to GHCR using PAT
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image - name: build regular container image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: build rootless container image
uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/riscv64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
file: Dockerfile.rootless file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta_rootless.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} annotations: ${{ steps.meta_rootless.outputs.annotations }}
+11 -10
View File
@@ -25,6 +25,9 @@ __debug_bin*
# Visual Studio # Visual Studio
/.vs/ /.vs/
# mise version managment tool
mise.toml
*.cgo1.go *.cgo1.go
*.cgo2.c *.cgo2.c
_cgo_defun.c _cgo_defun.c
@@ -52,6 +55,7 @@ cpu.out
*.log.*.gz *.log.*.gz
/gitea /gitea
/gitea-e2e
/gitea-vet /gitea-vet
/debug /debug
/integrations.test /integrations.test
@@ -64,13 +68,9 @@ cpu.out
/indexers /indexers
/log /log
/public/assets/img/avatar /public/assets/img/avatar
/tests/e2e-output
/tests/integration/gitea-integration-* /tests/integration/gitea-integration-*
/tests/integration/indexers-* /tests/integration/indexers-*
/tests/e2e/gitea-e2e-*
/tests/e2e/indexers-*
/tests/e2e/reports
/tests/e2e/test-artifacts
/tests/e2e/test-snapshots
/tests/*.ini /tests/*.ini
/tests/**/*.git/**/*.sample /tests/**/*.git/**/*.sample
/node_modules /node_modules
@@ -79,6 +79,7 @@ cpu.out
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*
/.pnpm-store /.pnpm-store
/public/assets/.vite
/public/assets/js /public/assets/js
/public/assets/css /public/assets/css
/public/assets/fonts /public/assets/fonts
@@ -86,10 +87,7 @@ cpu.out
/vendor /vendor
/VERSION /VERSION
/.air /.air
/.go-licenses
# Files and folders that were previously generated
/public/assets/img/webpack
# Snapcraft # Snapcraft
/gitea_a*.txt /gitea_a*.txt
@@ -118,7 +116,10 @@ prime/
/.goosehints /.goosehints
/.windsurfrules /.windsurfrules
/.github/copilot-instructions.md /.github/copilot-instructions.md
/AGENT.md
/CLAUDE.md
/llms.txt /llms.txt
# Ignore worktrees when working on multiple branches
.worktrees/
# A Makefile for custom make targets
Makefile.local
-51
View File
@@ -1,51 +0,0 @@
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
+15 -19
View File
@@ -6,15 +6,19 @@ linters:
default: none default: none
enable: enable:
- bidichk - bidichk
- bodyclose
- depguard - depguard
- dupl - dupl
- errcheck - errcheck
- forbidigo - forbidigo
- gocheckcompilerdirectives
- gocritic - gocritic
- govet - govet
- ineffassign - ineffassign
- mirror - mirror
- modernize
- nakedret - nakedret
- nilnil
- nolintlint - nolintlint
- perfsprint - perfsprint
- revive - revive
@@ -45,6 +49,8 @@ linters:
desc: do not use the ini package, use gitea's config system instead desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache - pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system desc: do not use the go-chi cache package, use gitea's cache system
- pkg: github.com/pkg/errors
desc: use builtin errors package instead
nolintlint: nolintlint:
allow-unused: false allow-unused: false
require-explanation: true require-explanation: true
@@ -55,38 +61,28 @@ linters:
disabled-checks: disabled-checks:
- ifElseChain - ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way. - singleCaseSwitch # Every time this occurred in the code, there was no other way.
- deprecatedComment # conflicts with go-swagger comments
revive: revive:
severity: error severity: error
rules: rules:
- name: atomic
- name: bare-return
- name: blank-imports - name: blank-imports
- name: constant-logical-expr - name: constant-logical-expr
- name: context-as-argument - name: context-as-argument
- name: context-keys-type - name: context-keys-type
- name: dot-imports - name: dot-imports
- name: duplicated-imports
- name: empty-lines - name: empty-lines
- name: error-naming
- name: error-return - name: error-return
- name: error-strings - name: error-strings
- name: errorf
- name: exported - name: exported
- name: identical-branches - name: identical-branches
- name: if-return - name: if-return
- name: increment-decrement - name: increment-decrement
- name: indent-error-flow
- name: modifies-value-receiver - name: modifies-value-receiver
- name: package-comments - name: package-comments
- name: range
- name: receiver-naming
- name: redefines-builtin-id - name: redefines-builtin-id
- name: string-of-int
- name: superfluous-else - name: superfluous-else
- name: time-naming - name: time-naming
- name: unconditional-recursion
- name: unexported-return - name: unexported-return
- name: unreachable-code
- name: var-declaration - name: var-declaration
- name: var-naming - name: var-naming
arguments: arguments:
@@ -107,6 +103,12 @@ linters:
- require-error - require-error
usetesting: usetesting:
os-temp-dir: true os-temp-dir: true
perfsprint:
concat-loop: false
govet:
enable:
- nilness
- unusedwrite
exclusions: exclusions:
generated: lax generated: lax
presets: presets:
@@ -118,16 +120,12 @@ linters:
- linters: - linters:
- dupl - dupl
- errcheck - errcheck
- gocyclo
- gosec
- staticcheck - staticcheck
- unparam - unparam
path: _test\.go path: _test\.go
- linters: - linters:
- dupl - dupl
- errcheck - errcheck
- gocyclo
- gosec
path: models/migrations/v path: models/migrations/v
- linters: - linters:
- forbidigo - forbidigo
@@ -139,12 +137,8 @@ linters:
- gocritic - gocritic
text: (?i)`ID' should not be capitalized text: (?i)`ID' should not be capitalized
- linters: - linters:
- deadcode
- unused - unused
text: (?i)swagger text: (?i)swagger
- linters:
- staticcheck
text: (?i)argument x is overwritten before first use
- linters: - linters:
- gocritic - gocritic
text: '(?i)commentFormatting: put a space between `//` and comment text' text: '(?i)commentFormatting: put a space between `//` and comment text'
@@ -153,6 +147,7 @@ linters:
text: '(?i)exitAfterDefer:' text: '(?i)exitAfterDefer:'
paths: paths:
- node_modules - node_modules
- .venv
- public - public
- web_src - web_src
- third_party$ - third_party$
@@ -172,6 +167,7 @@ formatters:
generated: lax generated: lax
paths: paths:
- node_modules - node_modules
- .venv
- public - public
- web_src - web_src
- third_party$ - third_party$
+1 -3
View File
@@ -21,9 +21,7 @@ rules:
comments-indentation: comments-indentation:
level: error level: error
document-start: document-start: disable
level: error
present: false
document-end: document-end:
present: false present: false
+536 -4
View File
@@ -4,7 +4,538 @@ This changelog goes through the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.com). been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.25.4](https://github.com/go-gitea/gitea/releases/tag/1.25.4) - 2026-01-15 ## [1.26.2](https://github.com/go-gitea/gitea/releases/tag/1.26.2) - 2026-05-20
* SECURITY
* fix(permissions): Fix reading permission (#37769)
* fix(actions): make artifact signature payloads unambiguous (#37707)
* fix: Unify public-only token filtering in API queries and repo access checks (#37118)
* fix: Add missed token scope checking (#37735)
* fix(oauth): bind token exchanges to the original client request (#37704)
* fix(oauth): strengthen PKCE validation and refresh token replay protection (#37706)
* fix(web): enforce token scopes on raw, media, and attachment downloads (#37698)
* fix(security): enforce wiki git writes and LFS token access at request time (#37695)
* feat(api): encrypt AWS creds (#37679)
* fix(deps): update dependency mermaid to v11.15.0 [security], add e2e test
* fix(packages): Add label for private and internal package and fix composor package source permission check (#37610)
* fix(git): Fix smart http request scope bug (#37583)
* Fix basic auth bug (#37503)
* Fix allow maintainer edit permission check (#37479) (#37484)
* Fix URL sanitization to handle schemeless credentials (#37440) (#37471)
* Fix attachment Content-Security-Policy (#37455) (#37464)
* chore(deps): bump go-git/go-git/v5 to 5.19.0 (#37608)
* BUGFIXES
* fix(pull): handle empty pull request files view to allow reviews (#37783)
* fix(markup): make RenderString never fail (#37779)
* fix: add natural sort to sortTreeViewNodes (#37772)
* fix: package creation unique conflict (#37774)
* fix!: add DEFAULT_TITLE_SOURCE setting for pull request title default behavior (#37465)
* fix: Allow direct commits for unprotected files with push restrictions (#37657)
* fix(actions): wrong assumption that run id always >= job id (#37737)
* fix(auth): set User-Agent on avatar fetch and sync avatar on link-account register (#37564) (#37588)
* fix(actions): deadlock between PrepareRunAndInsert and UpdateTaskByState (#37692)
* fix(repo): /generate must sync the branch table for the new repo (#37693)
* build: Fix snap build (1.26)
* fix(actions): run TransferLogs on UpdateLog{Rows:[], NoMore:true} (#37631)
* fix show correct mergebase
* fix: make clone URL respect public URL detection setting (#37615)
* fix: "run as root" check (#37622)
* chore(deps): update dependency go to v1.26.3 (#37601)
* Compare dropdown fails when selecting branch with no common merge-base (#37470)
* fix: treat email addresses case-insensitively (#37600)
* fix(actions): fix blank lines after ::endgroup:: (#37597)
* fix(actions): report individual step status in workflow job API response (#37592)
* fix: Invalid UTF-8 commit messages in JSON API responses (#37542)
* fix: use consistent GetUser family functions (#37553)
* fix(api): return 409 message instead of empty JSON for wrong commit id (#37572)
* fix(actions): prevent panic when workflow contains null jobs (#37570)
* Make ServeSetHeaders default to download attachment if filename exists (#37552) (#37555)
* Fix(actions): validate workflow param to prevent 500 error (#37546) (#37554)
* Don't unblock run-level-concurrency-blocked runs in the resolver (#37461) (#37538)
* Fix(packages): use file names for generic web downloads (#37514) (#37520)
* Fix merge autodetect can't close other PRs but only the last one when multiple PRs are pushed at once (#37512) (#37516)
* Fix update branch protection order (#37508) (#37513)
* Fix mCaptcha broken after Vite migration (#37492) (#37509)
* Fix review submission from single-commit PR view (#37475) (#37485)
* Fix scheduled action panic with null event payload (#37459) (#37466)
* Make GetPossibleUserByID can handle deleted user (#37430) (#37431)
* Remove excessive quote from terraform instructions (#37424) (#37426)
* Fix color regressions, add `priority` color (#37417) (#37421)
* MISC
* Add CurrentURL template variable back (#37444) (#37449)
## [1.26.1](https://github.com/go-gitea/gitea/releases/tag/v1.26.1) - 2026-04-21
* BUGFIXES
* Add event.schedule context for schedule actions task (#37320) (#37348)
* Fix an issue where changing an organization's visibility caused problems when users had forked its repositories. (#37324) (#37344)
* Use modern "git update-index --cacheinfo" syntax to support more file names (#37338) (#37343)
* Fix URL related escaping for oauth2 (#37334) (#37340)
* When the requested arch rpm is missing fall back to noarch (#37236) (#37339)
* Fix actions concurrency groups cross-branch leak (#37311) (#37331)
* Fix bug when accessing user badges (#37321) (#37329)
* Fix AppFullLink (#37325) (#37328)
* Fix container auth for public instance (#37290) (#37294)
* Enhance GetActionWorkflow to support fallback references (#37189) (#37283)
* Fix vite manifest update masking build errors (#37279) (#37310)
* Fix Mermaid diagrams failing when node labels contain line breaks (#37296) (#37299)
* Use TriggerEvent instead of Event in workflow runs API response for scheduled runs (#37288) #37360
* Add URL to Learn more about blocking a user. (#37355) #37367
* Fix button layout shift when collapsing file tree in editor (#37363) #37375
* Fix org team assignee/reviewer lookups for team member permissions (#37365) #37391
* Fix repo init README EOL (#37388) #37399
* Fix: dump with default zip type produces uncompressed zip (#37401)#37402
## [1.26.0](https://github.com/go-gitea/gitea/releases/tag/v1.26.0) - 2026-04-17
* BREAKING
* Correct swagger annotations for enums, status codes, and notification state (#37030)
* Remove GET API registration-token (#36801)
* Support Actions `concurrency` syntax (#32751)
* Make PUBLIC_URL_DETECTION default to "auto" (#36955)
* SECURITY
* Bound PageSize in `ListUnadoptedRepositories` (#36884)
* FEATURES
* Support Actions `concurrency` syntax (#32751)
* Add terraform state registry (#36710)
* Instance-wide (global) info banner and maintenance mode (#36571)
* Support rendering OpenAPI spec (#36449)
* Add keyboard shortcuts for repository file and code search (#36416)
* Add support for archive-upload rpc (#36391)
* Add ability to download subpath archive (#36371)
* Add workflow dependencies visualization (#26062) (#36248) & Restyle Workflow Graph (#36912)
* Automatic generation of release notes (#35977)
* Add "Go to file", "Delete Directory" to repo file list page (#35911)
* Introduce "config edit-ini" sub command to help maintaining INI config file (#35735)
* Add button to re-run failed jobs in Actions (#36924)
* Support actions and reusable workflows from private repos (#32562)
* Add summary to action runs view (#36883)
* Add user badges (#36752)
* Add configurable permissions for Actions automatic tokens (#36173)
* Add per-runner "Disable/Pause" (#36776)
* Feature non-zipped actions artifacts (action v7 / nodejs / npm v6.2.0) (#36786)
* PERFORMANCE
* WorkflowDispatch API optionally return runid (#36706)
* Add render cache for SVG icons (#36863)
* Load `mentionValues` asynchronously (#36739)
* Lazy-load some Vue components, fix heatmap chunk loading on every page (#36719)
* Load heatmap data asynchronously (#36622)
* Use prev/next pagination for user profile activities page to speed up (#36642)
* Refactor cat-file batch operations and support `--batch-command` approach (#35775)
* Use merge tree to detect conflicts when possible (#36400)
* ENHANCEMENTS
* Implement logout redirection for reverse proxy auth setups (#36085) (#37171)
* Adds option to force update new branch in contents routes (#35592)
* Add viewer controller for mermaid (zoom, drag) (#36557)
* Add code editor setting dropdowns (#36534)
* Add `elk` layout support to mermaid (#36486)
* Add resolve/unresolve review comment API endpoints (#36441)
* Allow configuring default PR base branch (fixes #36412) (#36425)
* Add support for RPM Errata (updateinfo.xml) (#37125)
* Require additional user confirmation for making repo private (#36959)
* Add `actions.WORKFLOW_DIRS` setting (#36619)
* Avoid opening new tab when downloading actions logs (#36740)
* Implements OIDC RP-Initiated Logout (#36724)
* Show workflow link (#37070)
* Desaturate dark theme background colors (#37056)
* Refactor "org teams" page and help new users to "add member" to an org (#37051)
* Add webhook name field to improve webhook identification (#37025) (#37040)
* Make task list checkboxes clickable in the preview tab (#37010)
* Improve severity labels in Actions logs and tweak colors (#36993)
* Linkify URLs in Actions workflow logs (#36986)
* Allow text selection on checkbox labels (#36970)
* Support dark/light theme images in markdown (#36922)
* Enable native dark mode for swagger-ui (#36899)
* Rework checkbox styling, remove `input` border hover effect (#36870)
* Refactor storage content-type handling of ServeDirectURL (#36804)
* Use "Enable Gravatar" but not "Disable" (#36771)
* Use case-insensitive matching for Git error "Not a valid object name" (#36728)
* Add "Copy Source" to markup comment menu (#36726)
* Change image transparency grid to CSS (#36711)
* Add "Run" prefix for unnamed action steps (#36624)
* Persist actions log time display settings in `localStorage` (#36623)
* Use first commit title for multi-commit PRs and fix auto-focus title field (#36606)
* Improve BuildCaseInsensitiveLike with lowercase (#36598)
* Improve diff highlighting (#36583)
* Exclude cancelled runs from failure-only email notifications (#36569)
* Use full-file highlighting for diff sections (#36561)
* Color command/error logs in Actions log (#36538)
* Add paging headers (#36521)
* Improve timeline entries for WIP prefix changes in pull requests (#36518)
* Add FOLDER_ICON_THEME configuration option (#36496)
* Normalize guessed languages for code highlighting (#36450)
* Add chunked transfer encoding support for LFS uploads (#36380)
* Indicate when only optional checks failed (#36367)
* Add 'allow_maintainer_edit' API option for creating a pull request (#36283)
* Support closing keywords with URL references (#36221)
* Improve diff file headers (#36215)
* Fix and enhance comment editor monospace toggle (#36181)
* Add git.DIFF_RENAME_SIMILARITY_THRESHOLD option (#36164)
* Add matching pair insertion to markdown textarea (#36121)
* Add sorting/filtering to admin user search API endpoint (#36112)
* Allow action user have read permission in public repo like other user (#36095)
* Disable matchBrackets in monaco (#36089)
* Use GitHub-style commit message for squash merge (#35987)
* Make composer registry support tar.gz and tar.bz2 and fix bugs (#35958)
* Add GITEA_PR_INDEX env variable to githooks (#35938)
* Add proper error message if session provider can not be created (#35520)
* Add button to copy file name in PR files (#35509)
* Move `X_FRAME_OPTIONS` setting from `cors` to `security` section (#30256)
* Add placeholder content for empty content page (#37114)
* Add `DEFAULT_DELETE_BRANCH_AFTER_MERGE` setting (#36917)
* Redirect to the only OAuth2 provider when no other login methods and fix various problems (#36901)
* Add admin badge to navbar avatar (#36790)
* Add `never` option to `PUBLIC_URL_DETECTION` configuration (#36785)
* Add background and run count to actions list page (#36707)
* Add icon to buttons "Close with Comment", "Close Pull Request", "Close Issue" (#36654)
* Add support for in_progress event in workflow_run webhook (#36979)
* Report commit status for pull_request_review events (#36589)
* Render merged pull request title as such in dashboard feed (#36479)
* Feature to be able to filter project boards by milestones (#36321)
* Use user id in noreply emails (#36550)
* Enable pagination on GiteaDownloader.getIssueReactions() (#36549)
* Remove striped tables in UI (#36509)
* Improve control char rendering and escape button styling (#37094)
* Support legacy run/job index-based URLs and refactor migration 326 (#37008)
* Add date to "No Contributions" tooltip (#36190)
* Show edit page confirmation dialog on tree view file change (#36130)
* Mention proc-receive in text for dashboard.resync_all_hooks func (#35991)
* Reuse selectable style for wiki (#35990)
* Support blue yellow colorblind theme (#35910)
* Support selecting theme on the footer (#35741)
* Improve online runner check (#35722)
* Add quick approve button on PR page (#35678)
* Enable commenting on expanded lines in PR diffs (#35662)
* Print PR-Title into tooltip for actions (#35579)
* Use explicit, stronger defaults for newly generated repo signing keys for Debian (#36236)
* Improve the compare page (#36261)
* Unify repo names in system notices (#36491)
* Move package settings to package instead of being tied to version (#37026)
* Add Actions API rerun endpoints for runs and jobs (#36768)
* Add branch_count to repository API (#35351) (#36743)
* Add created_by filter to SearchIssues (#36670)
* Allow admins to rename non-local users (#35970)
* Support updating branch via API (#35951)
* Add an option to automatically verify SSH keys from LDAP (#35927)
* Make "update file" API can create a new file when SHA is not set (#35738)
* Update issue.go with labels documentation (labels content, not ids) (#35522)
* Expose content_version for optimistic locking on issue and PR edits (#37035)
* Pass ServeHeaderOptions by value instead of pointer, fine tune httplib tests (#36982)
* BUGFIXES
* Frontend iframe renderer framework: 3D models, OpenAPI (#37233) (#37273)
* Fix CODEOWNERS absolute path matching. (#37244) (#37264)
* Swift registry metadata: preserve more JSON fields and accept empty metadata (#37254) (#37261)
* Fix user ssh key exporting and tests (#37256) (#37258)
* Fix team member avatar size and add tooltip (#37253)
* Fix commit title rendering in action run and blame (#37243) (#37251)
* Fix corrupted JSON caused by goccy library (#37214) (#37220)
* Add test for "fetch redirect", add CSS value validation for external render (#37207) (#37216)
* Fix incorrect concurrency check (#37205) (#37215)
* Fix handle missing base branch in PR commits API (#37193) (#37203)
* Fix encoding for Matrix Webhooks (#37190) (#37201)
* Fix handle fork-only commits in compare API (#37185) (#37199)
* Indicate form field readonly via background, fix RunUser config (#37175, #37180) (#37178)
* Report structurally invalid workflows to users (#37116) (#37164)
* Fix API not persisting pull request unit config when has_pull_requests is not set (#36718)
* Rename CSS variables and improve colorblind themes (#36353)
* Hide `add-matcher` and `remove-matcher` from actions job logs (#36520)
* Prevent navigation keys from triggering actions during IME composition (#36540)
* Fix vertical alignment of `.commit-sign-badge` children (#36570)
* Fix duplicate startup warnings in admin panel (#36641)
* Fix CODEOWNERS review request attribution using comment metadata (#36348)
* Fix HTML tags appearing in wiki table of contents (#36284)
* Fix various bugs (#37096)
* Fix various legacy problems (#37092)
* Fix RPM Registry 404 when package name contains 'package' (#37087)
* Merge some standalone Vite entries into index.js (#37085)
* Fix various problems (#37077)
* Fix issue label deletion with Actions tokens (#37013)
* Hide delete branch or tag buttons in mirror or archived repositories. (#37006)
* Fix org contact email not clearable once set (#36975)
* Fix a bug when forking a repository in an organization (#36950)
* Preserve sort order of exclusive labels from template repo (#36931)
* Make container registry support Apple Container (basic auth) (#36920)
* Fix the wrong push commits in the pull request when force push (#36914)
* Add class "list-header-filters" to the div for projects (#36889)
* Fix dbfs error handling (#36844)
* Fix incorrect viewed files counter if reverted change was viewed (#36819)
* Refactor avatar package, support default avatar fallback (#36788)
* Fix README symlink resolution in subdirectories like .github (#36775)
* Fix CSS stacking context issue in actions log (#36749)
* Add gpg signing for merge rebase and update by rebase (#36701)
* Delete non-exist branch should return 404 (#36694)
* Fix `TestActionsCollaborativeOwner` (#36657)
* Fix multi-arch Docker build SIGILL by splitting frontend stage (#36646)
* Fix linguist-detectable attribute being ignored for configuration files (#36640)
* Fix state desync in ComboMarkdownEditor (#36625)
* Unify DEFAULT_SHOW_FULL_NAME output in templates and dropdown (#36597)
* Pull Request Pusher should be the author of the merge (#36581)
* Fix various version parsing problems (#36553)
* Fix highlight diff result (#36539)
* Fix mirror sync parser and fix mirror messages (#36504)
* Fix bug when list pull request commits (#36485)
* Fix various bugs (#36446)
* Fix issue filter menu layout (#36426)
* Restrict branch naming when new change matches with protection rules (#36405)
* Fix link/origin referrer and login redirect (#36279)
* Generate IDs for HTML headings without id attribute (#36233)
* Use a migration test instead of a wrong test which populated the meta test repositories and fix a migration bug (#36160)
* Fix issue close timeline icon (#36138)
* Fix diff blob excerpt expansion (#35922)
* Fix external render (#35727)
* Fix review request webhook bug (#35339) (#35723)
* Fix shutdown waitgroup panic (#35676)
* Cleanup ActionRun creation (#35624)
* Fix possible bug when migrating issues/pull requests (#33487)
* Various fixes (#36697)
* Apply notify/register mail flags during install load (#37120)
* Repair duration display for bad stopped timestamps (#37121)
* Fix(upgrade.sh): use HTTPS for GPG key import and restore SELinux context after upgrade (#36930)
* Fix various trivial problems (#36921)
* Fix various trivial problems (#36953)
* Fix NuGet package upload error handling (#37074)
* Fix CodeQL code scanning alerts (#36858)
* Refactor issue sidebar and fix various problems (#37045)
* Fix various problems (#37029)
* Fix relative-time RangeError (#37021)
* Fix chroma lexer mapping (#36629)
* Fix typos and grammar in English locale (#36751)
* Fix milestone/project text overflow in issue sidebar (#36741)
* Fix `no-content` message not rendering after comment edit (#36733)
* Fix theme loading in development (#36605)
* Fix workflow run jobs API returning null steps (#36603)
* Fix timeline event layout overflow with long content (#36595)
* Fix minor UI issues in runner edit page (#36590)
* Fix incorrect vendored detections (#36508)
* Fix editorconfig not respected in PR Conversation view (#36492)
* Don't create self-references in merged PRs (#36490)
* Fix potential incorrect runID in run status update (#36437)
* Fix file-tree ui error when adding files to repo without commits (#36312)
* Improve image captcha contrast for dark mode (#36265)
* Fix panic in blame view when a file has only a single commit (#36230)
* Fix spelling error in migrate-storage cmd utility (#36226)
* Fix code highlighting on blame page (#36157)
* Fix nilnil in onedev downloader (#36154)
* Fix actions lint (#36029)
* Fix oauth2 session gob register (#36017)
* Fix Arch repo pacman.conf snippet (#35825)
* Fix a number of `strictNullChecks`-related issues (#35795)
* Fix URLJoin, markup render link reoslving, sign-in/up/linkaccount page common data (#36861)
* Hide delete directory button for mirror or archive repository and disable the menu item if user have no permission (#36384)
* Update message severity colors, fix navbar double border (#37019)
* Inline and lazy-load EasyMDE CSS, fix border colors (#36714)
* Closed milestones with no issues now show as 100% completed (#36220)
* Add test for ExtendCommentTreePathLength migration and fix bugs (#35791)
* Only turn links to current instance into hash links (#36237)
* Fix typos in code comments: doesnt, dont, wont (#36890)
* REFACTOR
* Clean up and improve non-gitea js error filter (#37148) (#37155)
* Always show owner/repo name in compare page dropdowns (#37172) (#37200)
* Remove dead CSS rules (#37173) (#37177)
* Replace Monaco with CodeMirror (#36764)
* Replace CSRF cookie with `CrossOriginProtection` (#36183)
* Replace index with id in actions routes (#36842)
* Remove unnecessary function parameter (#35765)
* Move jobparser from act repository to Gitea (#36699)
* Refactor compare router param parse (#36105)
* Optimize 'refreshAccesses' to perform update without removing then adding (#35702)
* Clean up checkbox cursor styles (#37016)
* Remove undocumented support of signing key in the repository git configuration file (#36143)
* Switch `cmd/` to use constructor functions. (#36962)
* Use `relative-time` to render absolute dates (#36238)
* Some refactors about GetMergeBase (#36186)
* Some small refactors (#36163)
* Use gitRepo as parameter instead of repopath when invoking sign functions (#36162)
* Move blame to gitrepo (#36161)
* Move some functions to gitrepo package to reduce RepoPath reference directly (#36126)
* Use gitrepo's clone and push when possible (#36093)
* Remove mermaid margin workaround (#35732)
* Move some functions to gitrepo package (#35543)
* Move GetDiverging functions to gitrepo (#35524)
* Use global lock instead of status pool for cron lock (#35507)
* Use explicit mux instead of DefaultServeMux (#36276)
* Use gitrepo's push function (#36245)
* Pass request context to generateAdditionalHeadersForIssue (#36274)
* Move assign project when creating pull request to the same database transaction (#36244)
* Move catfile batch to a sub package of git module (#36232)
* Use gitrepo.Repository instead of wikipath (#35398)
* Use experimental go json v2 library (#35392)
* Refactor template render (#36438)
* Refactor GetRepoRawDiffForFile to avoid unnecessary pipe or goroutine (#36434)
* Refactor text utility classes to Tailwind CSS (#36703)
* Refactor git command stdio pipe (#36422)
* Refactor git command context & pipeline (#36406)
* Refactor git command stdio pipe (#36393)
* Remove unused functions (#36672)
* Refactor Actions Token Access (#35688)
* Move commit related functions to gitrepo package (#35600)
* Move archive function to repo_model and gitrepo (#35514)
* Move some functions to gitrepo package (#35503)
* Use git model to detect whether branch exist instead of gitrepo method (#35459)
* Some refactor for repo path (#36251)
* Extract helper functions from SearchIssues (#36158)
* Refactor merge conan and container auth preserve actions taskID (#36560)
* Refactor Nuget Auth to reuse Basic Auth Token Validation (#36558)
* Refactor ActionsTaskID (#36503)
* Refactor auth middleware (#36848)
* Refactor code render and render control chars (#37078)
* Clean up AppURL, remove legacy origin-url webcomponent (#37090)
* Remove `util.URLJoin` and replace all callers with direct path concatenation (#36867)
* Replace legacy tw-flex utility classes with flex-text-block/inline (#36778)
* Mark unused&immature activitypub as "not implemented" (#36789)
* TESTING
* Add e2e tests for server push events (#36879)
* Rework e2e tests (#36634)
* Add e2e reaction test, improve accessibility, enable parallel testing (#37081)
* Increase e2e test timeouts on CI to fix flaky tests (#37053)
* BUILD
* Upgrade go-git to v5.18.0 (#37269)
* Replace rollup-plugin-license with rolldown-license-plugin (#37130) (#37158)
* Bump min go version to 1.26.2 (#37139) (#37143)
* Convert locale files from ini to json format (#35489)
* Bump golangci-lint to 2.7.2, enable modernize stringsbuilder (#36180)
* Port away from `flake-utils` (#35675)
* Remove nolint (#36252)
* Update the Unlicense copy to latest version (#36636)
* Update to go 1.26.0 and golangci-lint 2.9.0 (#36588)
* Replace `google/go-licenses` with custom generation (#36575)
* Update go dependencies (#36548)
* Bump appleboy/git-push-action from 1.0.0 to 1.2.0 (#36306)
* Remove fomantic form module (#36222)
* Bump setup-node to v6, re-enable cache (#36207)
* Bump crowdin/github-action from 1 to 2 (#36204)
* Revert "Bump alpine to 3.23 (#36185)" (#36202)
* Update chroma to v2.21.1 (#36201)
* Bump astral-sh/setup-uv from 6 to 7 (#36198)
* Bump docker/build-push-action from 5 to 6 (#36197)
* Bump aws-actions/configure-aws-credentials from 4 to 5 (#36196)
* Bump dev-hanz-ops/install-gh-cli-action from 0.1.0 to 0.2.1 (#36195)
* Add JSON linting (#36192)
* Enable dependabot for actions (#36191)
* Bump alpine to 3.23 (#36185)
* Update chroma to v2.21.0 (#36171)
* Update JS deps and eslint enhancements (#36147)
* Update JS deps (#36091)
* update golangci-lint to v2.7.0 (#36079)
* Update JS deps, fix deprecations (#36040)
* Update JS deps (#35978)
* Add toolchain directive to go.mod (#35901)
* Move `gitea-vet` to use `go tool` (#35878)
* Update to go 1.25.4 (#35877)
* Enable TypeScript `strictNullChecks` (#35843)
* Enable `vue/require-typed-ref` eslint rule (#35764)
* Update JS dependencies (#35759)
* Move `codeformat` folder to tools (#35758)
* Update dependencies (#35733)
* Bump happy-dom from 20.0.0 to 20.0.2 (#35677)
* Bump setup-go to v6 (#35660)
* Update JS deps, misc tweaks (#35643)
* Bump happy-dom from 19.0.2 to 20.0.0 (#35625)
* Use bundled version of spectral (#35573)
* Update JS and PY deps (#35565)
* Bump github.com/wneessen/go-mail from 0.6.2 to 0.7.1 (#35557)
* Migrate from webpack to vite (#37002)
* Update JS dependencies and misc tweaks (#37064)
* Update to eslint 10 (#36925)
* Optimize Docker build with dependency layer caching (#36864)
* Update JS deps (#36850)
* Update tool dependencies and fix new lint issues (#36702)
* Remove redundant linter rules (#36658)
* Move Fomantic dropdown CSS to custom module (#36530)
* Remove and forbid `@ts-expect-error` (#36513)
* Refactor git command stderr handling (#36402)
* Enable gocheckcompilerdirectives linter (#36156)
* Replace `lint-go-gopls` with additional `govet` linters (#36028)
* Update golangci-lint to v2.6.0 (#35801)
* Misc tool tweaks (#35734)
* Add cache to container build (#35697)
* Upgrade vite (#37126)
* Update `setup-uv` to v8.0.0 (#37101)
* Upgrade `go-git` to v5.17.2 and related dependencies (#37060)
* Raise minimum Node.js version to 22.18.0 (#37058)
* Upgrade `golang.org/x/image` to v0.38.0 (#37054)
* Update minimum go version to 1.26.1, golangci-lint to 2.11.2, fix test style (#36876)
* Enable eslint concurrency (#36878)
* Vendor relative-time-element as local web component (#36853)
* Update material-icon-theme v5.32.0 (#36832)
* Update Go dependencies (#36781)
* Upgrade minimatch (#36760)
* Remove i18n backport tool at the moment because of translation format changed (#36643)
* Update emoji data for Unicode 16 (#36596)
* Update JS dependencies, adjust webpack config, misc fixes (#36431)
* Update material-icon-theme to v5.31.0 (#36427)
* Update JS and PY deps (#36383)
* Bump alpine to 3.23, add platforms to `docker-dryrun` (#36379)
* Update JS deps (#36354)
* Update goldmark to v1.7.16 (#36343)
* Update chroma to v2.22.0 (#36342)
* DOCS
* Update AI Contribution Policy (#37022)
* Update AGENTS.md with additional guidelines (#37018)
* Add missing cron tasks to example ini (#37012)
* Add AI Contribution Policy to CONTRIBUTING.md (#36651)
* Minor punctuation improvement in CONTRIBUTING.md (#36291)
* Add documentation for markdown anchor post-processing (#36443)
* MISC
* Correct spelling (#36783)
* Update Nix flake (#37110)
* Update Nix flake (#37024)
* Add valid github scopes (#36977)
* Update Nix flake (#36943)
* Update Nix flake (#36902)
* Update Nix flake (#36857)
* Update Nix flake (#36787)
## [1.25.5](https://github.com/go-gitea/gitea/releases/tag/v1.25.5) - 2026-03-10
* SECURITY
* Toolchain Update to Go 1.25.6 (#36480) (#36487)
* Adjust the toolchain version (#36537) (#36542)
* Update toolchain to 1.25.8 for v1.25 (#36888)
* Prevent redirect bypasses via backslash-encoded paths (#36660) (#36716)
* Fix get release draft permission check (#36659) (#36715)
* Fix a bug user could change another user's primary email (#36586) (#36607)
* Fix OAuth2 authorization code expiry and reuse handling (#36797) (#36851)
* Add validation constraints for repository creation fields (#36671) (#36757)
* Fix bug to check whether user can update pull request branch or rebase branch (#36465) (#36838)
* Add migration http transport for push/sync mirror lfs (#36665) (#36691)
* Fix track time list permission check (#36662) (#36744)
* Fix track time issue id (#36664) (#36689)
* Fix path resolving (#36734) (#36746)
* Fix dump release asset bug (#36799) (#36839)
* Fix org permission API visibility checks for hidden members and private orgs (#36798) (#36841)
* Fix forwarded proto handling for public URL detection (#36810) (#36836)
* Add a git grep search timeout (#36809) (#36835)
* Fix oauth2 s256 (#36462) (#36477)
* ENHANCEMENTS
* Make `security-check` informational only (#36681) (#36852)
* Upgrade to github.com/cloudflare/circl 1.6.3, svgo 4.0.1, markdownlint-cli 0.48.0 (#36840)
* Add some validation on values provided to USER_DISABLED_FEATURES and EXTERNAL_USER_DISABLED_FEATURES (#36688) (#36692)
* Upgrade gogit to 5.16.5 (#36687)
* Add wrap to runner label list (#36565) (#36574)
* Add dnf5 command for Fedora in RPM package instructions (#36527) (#36572)
* Allow scroll propagation outside code editor (#36502) (#36510)
* BUGFIXES
* Fix non-admins unable to automerge PRs from forks (#36833) (#36843)
* Fix bug when pushing mirror with wiki (#36795) (#36807)
* Fix artifacts v4 backend upload problems (#36805) (#36834)
* Fix CRAN package version validation to allow more than 4 version components (#36813) (#36821)
* Fix force push time-line commit comments of pull request (#36653) (#36717)
* Fix SVG height calculation in diff viewer (#36748) (#36750)
* Fix push time bug (#36693) (#36713)
* Fix bug the protected branch rule name is conflicted with renamed branch name (#36650) (#36661)
* Fix bug when do LFS GC (#36500) (#36608)
* Fix focus lost bugs in the Monaco editor (#36609)
* Reprocess htmx content after loading more files (#36568) (#36577)
* Fix assignee sidebar links and empty placeholder (#36559) (#36563)
* Fix issues filter dropdown showing empty label scope section (#36535) (#36544)
* Fix various mermaid bugs (#36547) (#36552)
* Fix data race when uploading container blobs concurrently (#36524) (#36526)
* Correct spacing between username and bot label (#36473) (#36484)
## [1.25.4](https://github.com/go-gitea/gitea/releases/tag/v1.25.4) - 2026-01-15
* SECURITY * SECURITY
* Release attachments must belong to the intended repo (#36347) (#36375) * Release attachments must belong to the intended repo (#36347) (#36375)
@@ -20,7 +551,8 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Add more routes to the "expensive" list (#36290) * Add more routes to the "expensive" list (#36290)
* Make "commit statuses" API accept slashes in "ref" (#36264) (#36275) * Make "commit statuses" API accept slashes in "ref" (#36264) (#36275)
* BUGFIXES * BUGFIXES
* Fix markdown newline handling during IME composition (#36421) #36424 * Fix git http service handling (#36396)
* Fix markdown newline handling during IME composition (#36421) (#36424)
* Fix missing repository id when migrating release attachments (#36389) * Fix missing repository id when migrating release attachments (#36389)
* Fix bug when compare in the pull request (#36363) (#36372) * Fix bug when compare in the pull request (#36363) (#36372)
* Fix incorrect text content detection (#36364) (#36369) * Fix incorrect text content detection (#36364) (#36369)
@@ -35,7 +567,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix regression in writing authorized principals (#36213) (#36218) * Fix regression in writing authorized principals (#36213) (#36218)
* Fix WebAuthn error checking (#36219) (#36235) * Fix WebAuthn error checking (#36219) (#36235)
## [1.25.3](https://github.com/go-gitea/gitea/releases/tag/1.25.3) - 2025-12-17 ## [1.25.3](https://github.com/go-gitea/gitea/releases/tag/v1.25.3) - 2025-12-17
* SECURITY * SECURITY
* Bump toolchain to go1.25.5, misc fixes (#36082) * Bump toolchain to go1.25.5, misc fixes (#36082)
@@ -62,7 +594,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix error handling in mailer and wiki services (#36041) (#36053) * Fix error handling in mailer and wiki services (#36041) (#36053)
* Fix bugs when comparing and creating pull request (#36166) (#36144) * Fix bugs when comparing and creating pull request (#36166) (#36144)
## [1.25.2](https://github.com/go-gitea/gitea/releases/tag/1.25.2) - 2025-11-23 ## [1.25.2](https://github.com/go-gitea/gitea/releases/tag/v1.25.2) - 2025-11-23
* SECURITY * SECURITY
* Upgrade golang.org/x/crypto to 0.45.0 (#35985) (#35988) * Upgrade golang.org/x/crypto to 0.45.0 (#35985) (#35988)
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+30 -6
View File
@@ -4,6 +4,7 @@
- [Contribution Guidelines](#contribution-guidelines) - [Contribution Guidelines](#contribution-guidelines)
- [Introduction](#introduction) - [Introduction](#introduction)
- [AI Contribution Policy](#ai-contribution-policy)
- [Issues](#issues) - [Issues](#issues)
- [How to report issues](#how-to-report-issues) - [How to report issues](#how-to-report-issues)
- [Types of issues](#types-of-issues) - [Types of issues](#types-of-issues)
@@ -67,6 +68,21 @@ Sensitive security-related issues should be reported to [security@gitea.io](mail
For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/). For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/).
## AI Contribution Policy
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly.
1. Review AI-generated code closely before marking a pull request ready for review.
2. Manually test the changes and add appropriate automated tests where feasible.
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
4. Disclose AI-assisted content clearly.
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
## Issues ## Issues
### How to report issues ### How to report issues
@@ -80,7 +96,7 @@ The more detailed and specific you are, the faster we can fix the issue. \
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \ It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report. Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
Please be kind, remember that Gitea comes at no cost to you, and you're getting free help. Please be kindremember that Gitea comes at no cost to you, and you're getting free help.
### Types of issues ### Types of issues
@@ -167,7 +183,7 @@ Here's how to run the test suite:
- code lint - code lint
| | | | | |
| :-------------------- | :---------------------------------------------------------------- | | :-------------------- | :--------------------------------------------------------------------------- |
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) | |``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|``make lint-frontend`` | lint frontend files | |``make lint-frontend`` | lint frontend files |
|``make lint-backend`` | lint backend files | |``make lint-backend`` | lint backend files |
@@ -175,15 +191,23 @@ Here's how to run the test suite:
- run tests (we suggest running them on Linux) - run tests (we suggest running them on Linux)
| Command | Action | | | Command | Action | |
| :------------------------------------- | :----------------------------------------------- | ------------ | | :------------------------------------------ | :------------------------------------------------------- | ------------------------------------------- |
|``make test[\#SpecificTestName]`` | run unit test(s) | | |``make test[\#SpecificTestName]`` | run unit test(s) | |
|``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) | |``make test-sqlite[\#SpecificTestName]`` | run [integration](tests/integration) test(s) for SQLite | [More details](tests/integration/README.md) |
|``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/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: 3 on CI, 1 locally) |
## Translation ## Translation
All translation work happens on [Crowdin](https://translate.gitea.com). All translation work happens on [Crowdin](https://translate.gitea.com).
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini). 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).
It is synced regularly with Crowdin. \ It is synced regularly with Crowdin. \
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \ Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version. Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version.
+31 -30
View File
@@ -1,8 +1,15 @@
# Build stage # syntax=docker/dockerfile:1
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env # 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
ARG GOPROXY # Build backend for each target platform
ENV GOPROXY=${GOPROXY:-direct} FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
ARG GITEA_VERSION ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
@@ -12,37 +19,32 @@ ARG CGO_EXTRA_CFLAGS
# Build deps # Build deps
RUN apk --no-cache add \ RUN apk --no-cache add \
build-base \ build-base \
git \ git
nodejs \
npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
# Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea
COPY go.mod go.sum ./
RUN go mod download
# Use COPY instead of bind mount as read-only one breaks makefile state tracking and read-write one needs binary to be moved as it's discarded.
# ".git" directory is mounted separately later only for version data extraction.
COPY --exclude=.git/ . .
COPY --from=frontend-build /src/public/assets public/assets
# Checkout version if set # Build gitea, .git mount is required for version data
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ RUN --mount=type=cache,target="/root/.cache/go-build" \
&& make clean-all build --mount=type=bind,source=".git/",target=".git/" \
make backend
# Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go
# Copy local files
COPY docker/root /tmp/local COPY docker/root /tmp/local
# Set permissions # Set permissions for builds that made under windows which strips the executable bit from file
RUN chmod 755 /tmp/local/usr/bin/entrypoint \ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/usr/local/bin/gitea \ /tmp/local/usr/local/bin/* \
/tmp/local/etc/s6/gitea/* \ /tmp/local/etc/s6/gitea/* \
/tmp/local/etc/s6/openssh/* \ /tmp/local/etc/s6/openssh/* \
/tmp/local/etc/s6/.s6-svscan/* \ /tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea \ /go/src/code.gitea.io/gitea/gitea
/go/src/code.gitea.io/gitea/environment-to-ini
FROM docker.io/library/alpine:3.22 FROM docker.io/library/alpine:3.23 AS gitea
LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000 EXPOSE 22 3000
@@ -57,8 +59,7 @@ RUN apk --no-cache add \
s6 \ s6 \
sqlite \ sqlite \
su-exec \ su-exec \
gnupg \ gnupg
&& rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
-S -g 1000 \ -S -g 1000 \
@@ -72,14 +73,14 @@ RUN addgroup \
git && \ git && \
echo "git:*" | chpasswd -e echo "git:*" | chpasswd -e
COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
ENV USER=git ENV USER=git
ENV GITEA_CUSTOM=/data/gitea ENV GITEA_CUSTOM=/data/gitea
VOLUME ["/data"] VOLUME ["/data"]
# HINT: HEALTH-CHECK-ENDPOINT: don't use HEALTHCHECK, search this hint keyword for more information
ENTRYPOINT ["/usr/bin/entrypoint"] ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/usr/bin/s6-svscan", "/etc/s6"] CMD ["/usr/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
+28 -30
View File
@@ -1,46 +1,45 @@
# Build stage # syntax=docker/dockerfile:1
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env # 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
ARG GOPROXY # Build backend for each target platform
ENV GOPROXY=${GOPROXY:-direct} FROM docker.io/library/golang:1.26-alpine3.23 AS build-env
ARG GITEA_VERSION ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS="bindata timetzdata $TAGS" ENV TAGS="bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS ARG CGO_EXTRA_CFLAGS
#Build deps # Build deps
RUN apk --no-cache add \ RUN apk --no-cache add \
build-base \ build-base \
git \ git
nodejs \
npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
# Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea
COPY go.mod go.sum ./
RUN go mod download
# See the comments in Dockerfile
COPY --exclude=.git/ . .
COPY --from=frontend-build /src/public/assets public/assets
# Checkout version if set # Build gitea, .git mount is required for version data
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ RUN --mount=type=cache,target="/root/.cache/go-build" \
&& make clean-all build --mount=type=bind,source=".git/",target=".git/" \
make backend
# Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go
# Copy local files
COPY docker/rootless /tmp/local COPY docker/rootless /tmp/local
# Set permissions # Set permissions for builds that made under windows which strips the executable bit from file
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ RUN chmod 755 /tmp/local/usr/local/bin/* \
/tmp/local/usr/local/bin/docker-setup.sh \ /go/src/code.gitea.io/gitea/gitea
/tmp/local/usr/local/bin/gitea \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
FROM docker.io/library/alpine:3.22 FROM docker.io/library/alpine:3.23 AS gitea-rootless
LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000 EXPOSE 2222 3000
@@ -52,8 +51,7 @@ RUN apk --no-cache add \
git \ git \
curl \ curl \
gnupg \ gnupg \
openssh-keygen \ openssh-keygen
&& rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
-S -g 1000 \ -S -g 1000 \
@@ -71,7 +69,6 @@ RUN chown git:git /var/lib/gitea /etc/gitea
COPY --from=build-env /tmp/local / COPY --from=build-env /tmp/local /
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
# git:git # git:git
USER 1000:1000 USER 1000:1000
@@ -86,5 +83,6 @@ ENV HOME="/var/lib/gitea/git"
VOLUME ["/var/lib/gitea", "/etc/gitea"] VOLUME ["/var/lib/gitea", "/etc/gitea"]
WORKDIR /var/lib/gitea WORKDIR /var/lib/gitea
# HINT: HEALTH-CHECK-ENDPOINT: don't use HEALTHCHECK, search this hint keyword for more information
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD [] CMD []
+1
View File
@@ -64,3 +64,4 @@ metiftikci <metiftikci@hotmail.com> (@metiftikci)
Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX) Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp) Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)
TheFox <thefox0x7@gmail.com> (@TheFox0x7) TheFox <thefox0x7@gmail.com> (@TheFox0x7)
Nicolas <bircni@icloud.com> (@bircni)
+132 -251
View File
@@ -1,22 +1,9 @@
ifeq ($(USE_REPO_TEST_DIR),1)
# This rule replaces the whole Makefile when we're trying to use /tmp repository temporary files
location = $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
self := $(location)
%:
@tmpdir=`mktemp --tmpdir -d` ; \
echo Using temporary directory $$tmpdir for test repositories ; \
USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \
STATUS=$$? ; rm -r "$$tmpdir" ; exit $$STATUS
else
# This is the "normal" part of the Makefile
DIST := dist DIST := dist
DIST_DIRS := $(DIST)/binaries $(DIST)/release DIST_DIRS := $(DIST)/binaries $(DIST)/release
IMPORT := code.gitea.io/gitea
# By default use go's 1.25 experimental json v2 library when building
# TODO: remove when no longer experimental
export GOEXPERIMENT ?= jsonv2
GO ?= go GO ?= go
SHASUM ?= shasum -a 256 SHASUM ?= shasum -a 256
@@ -27,17 +14,14 @@ XGO_VERSION := go-1.25.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf00e56953556c6d80f8a01b286 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.8 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0
GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.20.0
DOCKER_IMAGE ?= gitea/gitea DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest DOCKER_TAG ?= latest
@@ -69,9 +53,11 @@ endif
ifeq ($(IS_WINDOWS),yes) ifeq ($(IS_WINDOWS),yes)
GOFLAGS := -v -buildmode=exe GOFLAGS := -v -buildmode=exe
EXECUTABLE ?= gitea.exe EXECUTABLE ?= gitea.exe
EXECUTABLE_E2E ?= gitea-e2e.exe
else else
GOFLAGS := -v GOFLAGS := -v
EXECUTABLE ?= gitea EXECUTABLE ?= gitea
EXECUTABLE_E2E ?= gitea-e2e
endif endif
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu) ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
@@ -82,7 +68,6 @@ endif
EXTRA_GOFLAGS ?= EXTRA_GOFLAGS ?=
MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1)
MAKE_EVIDENCE_DIR := .make_evidence MAKE_EVIDENCE_DIR := .make_evidence
GOTESTFLAGS ?= GOTESTFLAGS ?=
@@ -96,15 +81,6 @@ STORED_VERSION_FILE := VERSION
GITHUB_REF_TYPE ?= branch GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
# Enable typescript support in Node.js before 22.18
# TODO: Remove this once we can raise the minimum Node.js version to 22.18 (alpine >= 3.23)
NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | tr '.' ' '))
ifeq ($(shell test "$(NODE_VERSION)" -lt "022018000"; echo $$?),0)
NODE_VARS := NODE_OPTIONS="--experimental-strip-types"
else
NODE_VARS :=
endif
ifneq ($(GITHUB_REF_TYPE),branch) ifneq ($(GITHUB_REF_TYPE),branch)
VERSION ?= $(subst v,,$(GITHUB_REF_NAME)) VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
GITEA_VERSION ?= $(VERSION) GITEA_VERSION ?= $(VERSION)
@@ -128,17 +104,18 @@ ifeq ($(VERSION),main)
VERSION := main-nightly VERSION := main-nightly
endif endif
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(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,$(shell $(GO) list ./... | grep -v /vendor/))
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) FRONTEND_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.ts tailwind.config.ts FRONTEND_CONFIGS := vite.config.ts tailwind.config.ts
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css FRONTEND_DEST := public/assets/.vite/manifest.json
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts FRONTEND_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/.vite
FRONTEND_DEV_LOG_LEVEL ?= warn
BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.* BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.*
@@ -148,7 +125,6 @@ SVG_DEST_DIR := public/assets/img/svg
AIR_TMP_DIR := .air AIR_TMP_DIR := .air
GO_LICENSE_TMP_DIR := .go-licenses
GO_LICENSE_FILE := assets/go-licenses.json GO_LICENSE_FILE := assets/go-licenses.json
TAGS ?= TAGS ?=
@@ -157,24 +133,21 @@ TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR) TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests tools
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.ts tests/e2e ESLINT_FILES := web_src/js tools *.ts tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*)) SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.json .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml))
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.json
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go") GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go")
GO_SOURCES += $(GENERATED_GO_DEST) GO_SOURCES += $(GENERATED_GO_DEST)
# Force installation of playwright dependencies by setting this flag ESLINT_CONCURRENCY ?= 2
ifdef DEPS_PLAYWRIGHT
PLAYWRIGHT_FLAGS += --with-deps
endif
SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC := templates/swagger/v1_json.tmpl
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
@@ -195,26 +168,20 @@ TEST_MSSQL_DBNAME ?= gitea
TEST_MSSQL_USERNAME ?= sa TEST_MSSQL_USERNAME ?= sa
TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1 TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
# Include local Makefile
# Makefile.local is listed in .gitignore
sinclude Makefile.local
.PHONY: all .PHONY: all
all: build all: build
.PHONY: help .PHONY: help
help: Makefile ## print Makefile help information. help: Makefile ## print Makefile help information.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST) @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST)
@printf " \033[36m%-46s\033[0m %s\n" "test-e2e[#TestSpecificName]" "test end to end using playwright" @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[#TestSpecificName]" "run unit test" @printf " \033[36m%-46s\033[0m %s\n" "test[#TestSpecificName]" "run unit test"
@printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite" @printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite"
.PHONY: go-check
go-check:
$(eval MIN_GO_VERSION_STR := $(shell grep -Eo '^go\s+[0-9]+\.[0-9]+' go.mod | cut -d' ' -f2))
$(eval MIN_GO_VERSION := $(shell printf "%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' ')))
$(eval GO_VERSION := $(shell printf "%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9]+' | tr '.' ' ');))
@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
echo "Gitea requires Go $(MIN_GO_VERSION_STR) or greater to build. You can get it at https://go.dev/dl/"; \
exit 1; \
fi
.PHONY: git-check .PHONY: git-check
git-check: git-check:
@if git lfs >/dev/null 2>&1 ; then : ; else \ @if git lfs >/dev/null 2>&1 ; then : ; else \
@@ -222,39 +189,24 @@ git-check:
exit 1; \ exit 1; \
fi fi
.PHONY: node-check
node-check:
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
$(eval PNPM_MISSING := $(shell hash pnpm > /dev/null 2>&1 || echo 1))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" ]; then \
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater to build. You can get it at https://nodejs.org/en/download/"; \
exit 1; \
fi
@if [ "$(PNPM_MISSING)" = "1" ]; then \
echo "Gitea requires pnpm to build. You can install it at https://pnpm.io/installation"; \
exit 1; \
fi
.PHONY: clean-all .PHONY: clean-all
clean-all: clean ## delete backend, frontend and integration files clean-all: clean ## delete backend, frontend and integration files
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules rm -rf $(FRONTEND_DEST_ENTRIES) node_modules
.PHONY: clean .PHONY: clean
clean: ## delete backend and integration files clean: ## delete backend and integration files
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \ rm -rf $(EXECUTABLE) $(EXECUTABLE_E2E) $(DIST) $(BINDATA_DEST_WILDCARD) \
integrations*.test \ integrations*.test \
e2e*.test \
tests/integration/gitea-integration-* \ tests/integration/gitea-integration-* \
tests/integration/indexers-* \ tests/integration/indexers-* \
tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \ tests/sqlite.ini tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \
tests/e2e/gitea-e2e-*/ \ tests/e2e/gitea-e2e-*/ \
tests/e2e/indexers-*/ \ tests/e2e/indexers-*/ \
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
.PHONY: fmt .PHONY: fmt
fmt: ## format the Go and template code fmt: ## format the Go and template code
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' @GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run tools/code-batch-process.go gitea-fmt -w '{file-list}'
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl')) $(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only @# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
@# whitespace before it @# whitespace before it
@@ -272,19 +224,6 @@ fmt-check: fmt
exit 1; \ exit 1; \
fi fi
.PHONY: fix
fix: ## apply automated fixes to Go code
$(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./...
.PHONY: fix-check
fix-check: fix
@diff=$$(git diff --color=always $(GO_SOURCES)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fix' and commit the result:"; \
printf "%s" "$${diff}"; \
exit 1; \
fi
.PHONY: $(TAGS_EVIDENCE) .PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE): $(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR) @mkdir -p $(MAKE_EVIDENCE_DIR)
@@ -324,7 +263,7 @@ checks: checks-frontend checks-backend ## run various consistency checks
checks-frontend: lockfile-check svg-check ## check frontend files checks-frontend: lockfile-check svg-check ## check frontend files
.PHONY: checks-backend .PHONY: checks-backend
checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint-spell ## lint everything lint: lint-frontend lint-backend lint-spell ## lint everything
@@ -339,44 +278,48 @@ lint-frontend: lint-js lint-css ## lint frontend files
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
.PHONY: lint-backend .PHONY: lint-backend
lint-backend: lint-go lint-go-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files lint-backend: lint-go lint-go-gitea-vet lint-editorconfig ## lint backend files
.PHONY: lint-backend-fix .PHONY: lint-backend-fix
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules ## lint js files lint-js: node_modules ## lint js and ts files
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES)
$(NODE_VARS) pnpm exec vue-tsc pnpm exec vue-tsc
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules ## lint js files and fix issues lint-js-fix: node_modules ## lint js and ts files and fix issues
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) --fix pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES) --fix
$(NODE_VARS) pnpm exec vue-tsc pnpm exec vue-tsc
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules ## lint css files lint-css: node_modules ## lint css files
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES)
.PHONY: lint-css-fix .PHONY: lint-css-fix
lint-css-fix: node_modules ## lint css files and fix issues lint-css-fix: node_modules ## lint css files and fix issues
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
.PHONY: lint-swagger .PHONY: lint-swagger
lint-swagger: node_modules ## lint swagger files lint-swagger: node_modules ## lint swagger files
$(NODE_VARS) pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC) pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC)
.PHONY: lint-md .PHONY: lint-md
lint-md: node_modules ## lint markdown files lint-md: node_modules ## lint markdown files
$(NODE_VARS) pnpm exec markdownlint *.md 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-spell .PHONY: lint-spell
lint-spell: ## lint spelling lint-spell: ## lint spelling
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES) @git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error
.PHONY: lint-spell-fix .PHONY: lint-spell-fix
lint-spell-fix: ## lint spelling and fix issues lint-spell-fix: ## lint spelling and fix issues
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES) @git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w
.PHONY: lint-go .PHONY: lint-go
lint-go: ## lint go files lint-go: ## lint go files
@@ -396,13 +339,7 @@ lint-go-windows:
.PHONY: lint-go-gitea-vet .PHONY: lint-go-gitea-vet
lint-go-gitea-vet: ## lint go files with gitea-vet lint-go-gitea-vet: ## lint go files with gitea-vet
@echo "Running gitea-vet..." @echo "Running gitea-vet..."
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet @$(GO) vet -vettool="$(shell GOOS= GOARCH= go tool -n 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)
.PHONY: lint-editorconfig .PHONY: lint-editorconfig
lint-editorconfig: lint-editorconfig:
@@ -422,17 +359,24 @@ lint-templates: .venv node_modules ## lint template files
lint-yaml: .venv ## lint yaml files lint-yaml: .venv ## lint yaml files
@uv run --frozen yamllint -s . @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
.PHONY: watch .PHONY: watch
watch: ## watch everything and continuously rebuild watch: ## watch everything and continuously rebuild
@bash tools/watch.sh @bash tools/watch.sh
.PHONY: watch-frontend .PHONY: watch-frontend
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild watch-frontend: node_modules ## start vite dev server for frontend
@rm -rf $(WEBPACK_DEST_ENTRIES) NODE_ENV=development pnpm exec vite --logLevel $(FRONTEND_DEV_LOG_LEVEL)
NODE_ENV=development $(NODE_VARS) pnpm exec webpack --watch --progress --disable-interpret
.PHONY: watch-backend .PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild watch-backend: ## watch backend files and continuously rebuild
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
.PHONY: test .PHONY: test
@@ -445,7 +389,7 @@ test-backend: ## test backend files
.PHONY: test-frontend .PHONY: test-frontend
test-frontend: node_modules ## test frontend files test-frontend: node_modules ## test frontend files
$(NODE_VARS) pnpm exec vitest pnpm exec vitest
.PHONY: test-check .PHONY: test-check
test-check: test-check:
@@ -468,7 +412,7 @@ test\#%:
coverage: coverage:
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
$(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all $(GO) run tools/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all
.PHONY: unit-test-coverage .PHONY: unit-test-coverage
unit-test-coverage: unit-test-coverage:
@@ -498,25 +442,20 @@ tidy-check: tidy
go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses
$(GO_LICENSE_FILE): go.mod go.sum $(GO_LICENSE_FILE): go.mod go.sum
@rm -rf $(GO_LICENSE_FILE) GO=$(GO) $(GO) run build/generate-go-licenses.go $(GO_LICENSE_FILE)
$(GO) install $(GO_LICENSES_PACKAGE)
-GOOS=linux CGO_ENABLED=1 go-licenses save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
$(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE)
@rm -rf $(GO_LICENSE_TMP_DIR)
generate-ini-sqlite: generate-ini-sqlite:
sed -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ sed -e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-sqlite|g' \
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
tests/sqlite.ini.tmpl > tests/sqlite.ini tests/sqlite.ini.tmpl > tests/sqlite.ini
.PHONY: test-sqlite .PHONY: test-sqlite
test-sqlite: integrations.sqlite.test generate-ini-sqlite test-sqlite: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test
.PHONY: test-sqlite\#% .PHONY: test-sqlite\#%
test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*) GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
.PHONY: test-sqlite-migration .PHONY: test-sqlite-migration
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
@@ -526,18 +465,17 @@ generate-ini-mysql:
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \ -e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
-e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \ -e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \
-e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \ -e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ -e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-mysql|g' \
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
tests/mysql.ini.tmpl > tests/mysql.ini tests/mysql.ini.tmpl > tests/mysql.ini
.PHONY: test-mysql .PHONY: test-mysql
test-mysql: integrations.mysql.test generate-ini-mysql test-mysql: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test
.PHONY: test-mysql\#% .PHONY: test-mysql\#%
test-mysql\#%: integrations.mysql.test generate-ini-mysql test-mysql\#%: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*) GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
.PHONY: test-mysql-migration .PHONY: test-mysql-migration
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test
@@ -549,18 +487,17 @@ generate-ini-pgsql:
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \ -e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \ -e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \
-e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \ -e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ -e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-pgsql|g' \
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
tests/pgsql.ini.tmpl > tests/pgsql.ini tests/pgsql.ini.tmpl > tests/pgsql.ini
.PHONY: test-pgsql .PHONY: test-pgsql
test-pgsql: integrations.pgsql.test generate-ini-pgsql test-pgsql: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test
.PHONY: test-pgsql\#% .PHONY: test-pgsql\#%
test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*) GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
.PHONY: test-pgsql-migration .PHONY: test-pgsql-migration
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test
@@ -570,89 +507,53 @@ generate-ini-mssql:
-e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \ -e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \
-e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \ -e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \
-e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \ -e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ -e 's|{{WORK_PATH}}|$(CURDIR)/tests/$(or $(TEST_TYPE),integration)/gitea-$(or $(TEST_TYPE),integration)-mssql|g' \
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
tests/mssql.ini.tmpl > tests/mssql.ini tests/mssql.ini.tmpl > tests/mssql.ini
.PHONY: test-mssql .PHONY: test-mssql
test-mssql: integrations.mssql.test generate-ini-mssql test-mssql: integrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test
.PHONY: test-mssql\#% .PHONY: test-mssql\#%
test-mssql\#%: integrations.mssql.test generate-ini-mssql test-mssql\#%: integrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*) GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
.PHONY: test-mssql-migration .PHONY: test-mssql-migration
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright .PHONY: playwright
playwright: deps-frontend playwright: deps-frontend
$(NODE_VARS) pnpm exec playwright install $(PLAYWRIGHT_FLAGS) @# on GitHub Actions VMs, playwright's system deps are pre-installed
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e%
test-e2e%: TEST_TYPE ?= e2e
# Clear display env variable. Otherwise, chromium tests can fail.
DISPLAY=
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: test-e2e-sqlite test-e2e: playwright $(EXECUTABLE_E2E)
@EXECUTABLE=$(EXECUTABLE_E2E) ./tools/test-e2e.sh $(GITEA_TEST_E2E_FLAGS)
.PHONY: test-e2e-sqlite
test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test
.PHONY: test-e2e-sqlite\#%
test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e/$*
.PHONY: test-e2e-mysql
test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test
.PHONY: test-e2e-mysql\#%
test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e/$*
.PHONY: test-e2e-pgsql
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test
.PHONY: test-e2e-pgsql\#%
test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e/$*
.PHONY: test-e2e-mssql
test-e2e-mssql: playwright e2e.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test
.PHONY: test-e2e-mssql\#%
test-e2e-mssql\#%: playwright e2e.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e/$*
.PHONY: bench-sqlite .PHONY: bench-sqlite
bench-sqlite: integrations.sqlite.test generate-ini-sqlite bench-sqlite: integrations.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . GITEA_TEST_CONF=tests/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-mysql .PHONY: bench-mysql
bench-mysql: integrations.mysql.test generate-ini-mysql bench-mysql: integrations.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . GITEA_TEST_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-mssql .PHONY: bench-mssql
bench-mssql: integrations.mssql.test generate-ini-mssql bench-mssql: integrations.mssql.test generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . GITEA_TEST_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: bench-pgsql .PHONY: bench-pgsql
bench-pgsql: integrations.pgsql.test generate-ini-pgsql bench-pgsql: integrations.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . GITEA_TEST_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench .
.PHONY: integration-test-coverage .PHONY: integration-test-coverage
integration-test-coverage: integrations.cover.test generate-ini-mysql integration-test-coverage: integrations.cover.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out GITEA_TEST_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
.PHONY: integration-test-coverage-sqlite .PHONY: integration-test-coverage-sqlite
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out GITEA_TEST_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
integrations.mysql.test: git-check $(GO_SOURCES) integrations.mysql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
@@ -675,66 +576,54 @@ integrations.cover.sqlite.test: git-check $(GO_SOURCES)
.PHONY: migrations.mysql.test .PHONY: migrations.mysql.test
migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test GITEA_TEST_CONF=tests/mysql.ini ./migrations.mysql.test
.PHONY: migrations.pgsql.test .PHONY: migrations.pgsql.test
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test GITEA_TEST_CONF=tests/pgsql.ini ./migrations.pgsql.test
.PHONY: migrations.mssql.test .PHONY: migrations.mssql.test
migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./migrations.mssql.test GITEA_TEST_CONF=tests/mssql.ini ./migrations.mssql.test
.PHONY: migrations.sqlite.test .PHONY: migrations.sqlite.test
migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)' $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./migrations.sqlite.test GITEA_TEST_CONF=tests/sqlite.ini ./migrations.sqlite.test
.PHONY: migrations.individual.mysql.test .PHONY: migrations.individual.mysql.test
migrations.individual.mysql.test: $(GO_SOURCES) migrations.individual.mysql.test: $(GO_SOURCES) generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) GITEA_TEST_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
.PHONY: migrations.individual.sqlite.test\#% .PHONY: migrations.individual.sqlite.test\#%
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
.PHONY: migrations.individual.pgsql.test .PHONY: migrations.individual.pgsql.test
migrations.individual.pgsql.test: $(GO_SOURCES) migrations.individual.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) GITEA_TEST_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
.PHONY: migrations.individual.pgsql.test\#% .PHONY: migrations.individual.pgsql.test\#%
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* GITEA_TEST_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
.PHONY: migrations.individual.mssql.test .PHONY: migrations.individual.mssql.test
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) GITEA_TEST_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
.PHONY: migrations.individual.mssql.test\#% .PHONY: migrations.individual.mssql.test\#%
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* GITEA_TEST_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
.PHONY: migrations.individual.sqlite.test .PHONY: migrations.individual.sqlite.test
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
.PHONY: migrations.individual.sqlite.test\#% .PHONY: migrations.individual.sqlite.test\#%
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* GITEA_TEST_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
e2e.mysql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test
e2e.pgsql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test
e2e.mssql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mssql.test
e2e.sqlite.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)'
.PHONY: check .PHONY: check
check: test check: test
@@ -747,10 +636,10 @@ install: $(wildcard *.go)
build: frontend backend ## build everything build: frontend backend ## build everything
.PHONY: frontend .PHONY: frontend
frontend: $(WEBPACK_DEST) ## build frontend files frontend: $(FRONTEND_DEST) ## build frontend files
.PHONY: backend .PHONY: backend
backend: go-check generate-backend $(EXECUTABLE) ## build backend files backend: generate-backend $(EXECUTABLE) ## build backend files
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend # We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
.PHONY: generate .PHONY: generate
@@ -766,14 +655,17 @@ generate-go: $(TAGS_PREREQ)
.PHONY: security-check .PHONY: security-check
security-check: security-check:
go run $(GOVULNCHECK_PACKAGE) -show color ./... GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./... || true
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),) ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
$(error pam support set via TAGS doesn't support static builds) $(error pam support set via TAGS does not support static builds)
endif endif
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@ CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
$(EXECUTABLE_E2E): $(GO_SOURCES) $(FRONTEND_DEST)
CGO_ENABLED=1 $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TEST_TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
.PHONY: release .PHONY: release
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
@@ -844,15 +736,12 @@ deps-tools: ## install tool dependencies
$(GO) install $(MISSPELL_PACKAGE) & \ $(GO) install $(MISSPELL_PACKAGE) & \
$(GO) install $(SWAGGER_PACKAGE) & \ $(GO) install $(SWAGGER_PACKAGE) & \
$(GO) install $(XGO_PACKAGE) & \ $(GO) install $(XGO_PACKAGE) & \
$(GO) install $(GO_LICENSES_PACKAGE) & \
$(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(GOVULNCHECK_PACKAGE) & \
$(GO) install $(ACTIONLINT_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE) & \
$(GO) install $(GOPLS_PACKAGE) & \
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
wait wait
node_modules: pnpm-lock.yaml node_modules: pnpm-lock.yaml
$(NODE_VARS) pnpm install --frozen-lockfile pnpm install --frozen-lockfile
@touch node_modules @touch node_modules
.venv: uv.lock .venv: uv.lock
@@ -860,36 +749,41 @@ node_modules: pnpm-lock.yaml
@touch .venv @touch .venv
.PHONY: update .PHONY: update
update: update-js update-py ## update js and py dependencies update: update-go update-js update-py ## update dependencies
.PHONY: update-go
update-go: ## update go dependencies
$(GO) get -u ./...
$(MAKE) tidy
.PHONY: update-js .PHONY: update-js
update-js: node-check | node_modules ## update js dependencies update-js: node_modules ## update js dependencies
$(NODE_VARS) pnpm exec updates -u -f package.json pnpm exec updates -u -f package.json
rm -rf node_modules pnpm-lock.yaml rm -rf node_modules pnpm-lock.yaml
$(NODE_VARS) pnpm install pnpm install
$(NODE_VARS) pnpm exec nolyfill install pnpm exec nolyfill install
$(NODE_VARS) pnpm install pnpm install
@touch node_modules @touch node_modules
.PHONY: update-py .PHONY: update-py
update-py: node-check | node_modules ## update py dependencies update-py: node_modules ## update py dependencies
$(NODE_VARS) pnpm exec updates -u -f pyproject.toml pnpm exec updates -u -f pyproject.toml
rm -rf .venv uv.lock rm -rf .venv uv.lock
uv sync uv sync
@touch .venv @touch .venv
.PHONY: webpack .PHONY: vite
webpack: $(WEBPACK_DEST) ## build webpack files vite: $(FRONTEND_DEST) ## build vite files
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml $(FRONTEND_DEST): $(FRONTEND_SOURCES) $(FRONTEND_CONFIGS) pnpm-lock.yaml
@$(MAKE) -s node-check node_modules @$(MAKE) -s node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(FRONTEND_DEST_ENTRIES)
@echo "Running webpack..." @echo "Running vite build..."
@BROWSERSLIST_IGNORE_OLD_DATA=true $(NODE_VARS) pnpm exec webpack --disable-interpret @pnpm exec vite build
@touch $(WEBPACK_DEST) @touch $(FRONTEND_DEST)
.PHONY: svg .PHONY: svg
svg: node-check | node_modules ## build svg files svg: node_modules ## build svg files
rm -rf $(SVG_DEST_DIR) rm -rf $(SVG_DEST_DIR)
node tools/generate-svg.ts node tools/generate-svg.ts
@@ -905,7 +799,7 @@ svg-check: svg
.PHONY: lockfile-check .PHONY: lockfile-check
lockfile-check: lockfile-check:
$(NODE_VARS) pnpm install --frozen-lockfile pnpm install --frozen-lockfile
@diff=$$(git diff --color=always pnpm-lock.yaml); \ @diff=$$(git diff --color=always pnpm-lock.yaml); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "pnpm-lock.yaml is inconsistent with package.json"; \ echo "pnpm-lock.yaml is inconsistent with package.json"; \
@@ -914,16 +808,6 @@ lockfile-check:
exit 1; \ exit 1; \
fi fi
.PHONY: update-translations
update-translations:
mkdir -p ./translations
cd ./translations && curl -L https://crowdin.com/download/project/gitea.zip > gitea.zip && unzip gitea.zip
rm ./translations/gitea.zip
$(SED_INPLACE) -e 's/="/=/g' -e 's/"$$//g' ./translations/*.ini
$(SED_INPLACE) -e 's/\\"/"/g' ./translations/*.ini
mv ./translations/*.ini ./options/locale/
rmdir ./translations
.PHONY: generate-gitignore .PHONY: generate-gitignore
generate-gitignore: ## update gitignore files generate-gitignore: ## update gitignore files
$(GO) run build/generate-gitignores.go $(GO) run build/generate-gitignores.go
@@ -945,9 +829,6 @@ docker:
docker build --disable-content-trust=false -t $(DOCKER_REF) . docker build --disable-content-trust=false -t $(DOCKER_REF) .
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" . # support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
# This endif closes the if at the top of the file
endif
# Disable parallel execution because it would break some targets that don't # Disable parallel execution because it would break some targets that don't
# specify exact dependencies like 'backend' which does currently not depend # specify exact dependencies like 'backend' which does currently not depend
# on 'frontend' to enable Node.js-less builds from source tarballs. # on 'frontend' to enable Node.js-less builds from source tarballs.
-1
View File
@@ -8,7 +8,6 @@
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") [![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") [![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") [![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") [![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md) [繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md)
+1 -2
View File
@@ -8,7 +8,6 @@
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") [![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") [![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") [![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") [![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [繁體中文](./README.zh-tw.md) [English](./README.md) | [繁體中文](./README.zh-tw.md)
@@ -46,7 +45,7 @@
`build` 目标分为两个子目标: `build` 目标分为两个子目标:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。 - `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 - `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本以及 [pnpm](https://pnpm.io/installation)
需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。 需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。
+1 -2
View File
@@ -8,7 +8,6 @@
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") [![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") [![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") [![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") [![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [简体中文](./README.zh-cn.md) [English](./README.md) | [简体中文](./README.zh-cn.md)
@@ -46,7 +45,7 @@
`build` 目標分為兩個子目標: `build` 目標分為兩個子目標:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。 - `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 - `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本以及 [pnpm](https://pnpm.io/installation)
需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。 需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。
+11483 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-14
View File
@@ -1,14 +0,0 @@
// 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 Makefile build.
// These libraries will not be included in a normal compilation.
import (
// for vet
_ "code.gitea.io/gitea-vet"
)
@@ -1,115 +0,0 @@
// 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("----")
}
}
}
}
}
+4 -4
View File
@@ -24,8 +24,8 @@ import (
) )
const ( const (
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json" gemojiURL = "https://raw.githubusercontent.com/rhysd/gemoji/537ff2d7e0496e9964824f7f73ec7ece88c9765a/db/emoji.json"
maxUnicodeVersion = 15 maxUnicodeVersion = 16
) )
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
@@ -149,8 +149,8 @@ func generate() ([]byte, error) {
} }
// write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet) // write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet)
file, _ := json.Marshal(data) file, _ := json.MarshalIndent(data, "", " ")
_ = os.WriteFile("assets/emoji.json", file, 0o644) _ = os.WriteFile("assets/emoji.json", append(file, '\n'), 0o644)
// Add skin tones to emoji that support it // Add skin tones to emoji that support it
var ( var (
@@ -8,99 +8,220 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs"
"os" "os"
"path" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices"
"sort" "sort"
"strings" "strings"
"code.gitea.io/gitea/modules/container"
) )
// regexp is based on go-license, excluding README and NOTICE // regexp is based on go-license, excluding README and NOTICE
// https://github.com/google/go-licenses/blob/master/licenses/find.go // https://github.com/google/go-licenses/blob/master/licenses/find.go
// also defined in vite.config.ts
var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`) var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`)
// primaryLicenseRe matches exact primary license filenames without suffixes.
// When a directory has both primary and variant files (e.g. LICENSE and
// LICENSE.docs), only the primary files are kept.
var primaryLicenseRe = regexp.MustCompile(`^(?i)(LICEN[SC]E|COPYING)$`)
// ignoredNames are LicenseEntry.Name values to exclude from the output.
var ignoredNames = map[string]bool{
"code.gitea.io/gitea": true,
"code.gitea.io/gitea/options/license": true,
}
var excludedExt = map[string]bool{
".gitignore": true,
".go": true,
".mod": true,
".sum": true,
".toml": true,
".yaml": true,
".yml": true,
}
type ModuleInfo struct {
Path string
Dir string
PkgDirs []string // directories of packages imported from this module
}
type LicenseEntry struct { type LicenseEntry struct {
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path"` Path string `json:"path"`
LicenseText string `json:"licenseText"` LicenseText string `json:"licenseText"`
} }
func main() { // getModules returns all dependency modules with their local directory paths
if len(os.Args) != 3 { // and the package directories used from each module.
fmt.Println("usage: go run generate-go-licenses.go <base-dir> <out-json-file>") 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)
os.Exit(1) os.Exit(1)
} }
base, out := os.Args[1], os.Args[2] var modules []ModuleInfo
seen := make(map[string]int) // module path -> index in modules
// Add ext for excluded files because license_test.go will be included for some reason. for _, line := range strings.Split(string(output), "\n") {
// And there are more files that should be excluded, check with: line = strings.TrimSpace(line)
// if line == "" {
// go run github.com/google/go-licenses@v1.6.0 save . --force --save_path=.go-licenses 2>/dev/null continue
// 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")
var paths []string
err := filepath.WalkDir(base, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
} }
if entry.IsDir() || !licenseRe.MatchString(entry.Name()) || excludedExt.Contains(filepath.Ext(entry.Name())) { parts := strings.Split(line, "\t")
return nil if len(parts) != 3 {
continue
} }
paths = append(paths, path) modPath, modDir, pkgDir := parts[0], parts[1], parts[2]
return nil 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},
}) })
if err != nil { }
panic(err) }
return modules
}
// 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)
// First, collect root-level license files.
entries = append(entries, scanDirForLicenses(mod.Dir, mod.Path, "")...)
for _, e := range entries {
seenTexts[e.LicenseText] = true
} }
sort.Strings(paths) // 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 var entries []LicenseEntry
for _, filePath := range paths { for _, entry := range dirEntries {
licenseText, err := os.ReadFile(filePath) if entry.IsDir() {
if err != nil { continue
panic(err)
} }
name := entry.Name()
pkgPath := filepath.ToSlash(filePath) if !licenseRe.MatchString(name) {
pkgPath = strings.TrimPrefix(pkgPath, base+"/") continue
pkgName := path.Dir(pkgPath) }
if excludedExt[strings.ToLower(filepath.Ext(name))] {
// 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 continue
} }
content, err := os.ReadFile(filepath.Join(dir, name))
if err != nil {
continue
}
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
}
}
entries = append(entries, LicenseEntry{ entries = append(entries, LicenseEntry{
Name: pkgName, Name: entryName,
Path: pkgPath, Path: entryPath,
LicenseText: string(licenseText), LicenseText: string(content),
}) })
} }
// When multiple license files exist, prefer primary files (e.g. LICENSE)
// over variants with suffixes (e.g. LICENSE.docs, LICENSE-2.0.txt).
// If no primary file exists, keep only the first variant.
if len(entries) > 1 {
var primary []LicenseEntry
for _, e := range entries {
fileName := e.Path[strings.LastIndex(e.Path, "/")+1:]
if primaryLicenseRe.MatchString(fileName) {
primary = append(primary, e)
}
}
if len(primary) > 0 {
return primary
}
return entries[:1]
}
return entries
}
func main() {
if len(os.Args) != 2 {
fmt.Println("usage: go run generate-go-licenses.go <out-json-file>")
os.Exit(1)
}
out := os.Args[1]
goCmd := "go"
if env := os.Getenv("GO"); env != "" {
goCmd = env
}
modules := getModules(goCmd)
var entries []LicenseEntry
for _, mod := range modules {
entries = append(entries, findLicenseFiles(mod)...)
}
entries = slices.DeleteFunc(entries, func(e LicenseEntry) bool {
return ignoredNames[e.Name]
})
sort.Slice(entries, func(i, j int) bool {
return entries[i].Path < entries[j].Path
})
jsonBytes, err := json.MarshalIndent(entries, "", " ") jsonBytes, err := json.MarshalIndent(entries, "", " ")
if err != nil { if err != nil {
panic(err) panic(err)
+7 -37
View File
@@ -1,52 +1,22 @@
#!/bin/sh #!/bin/sh
# this script runs in alpine image which only has `sh` shell # this script runs in alpine image which only has `sh` shell
if [ ! -f ./options/locale/locale_en-US.json ]; then
set +e
if sed --version 2>/dev/null | grep -q GNU; then
SED_INPLACE="sed -i"
else
SED_INPLACE="sed -i ''"
fi
set -e
if [ ! -f ./options/locale/locale_en-US.ini ]; then
echo "please run this script in the root directory of the project" echo "please run this script in the root directory of the project"
exit 1 exit 1
fi fi
mv ./options/locale/locale_en-US.ini ./options/ mv ./options/locale/locale_en-US.json ./options/
# the "ini" library for locale has many quirks, its behavior is different from Crowdin.
# see i18n_test.go for more details
# this script helps to unquote the Crowdin outputs for the quirky ini library
# * find all `key="...\"..."` lines
# * remove the leading quote
# * remove the trailing quote
# * unescape the quotes
# * eg: key="...\"..." => key=..."...
$SED_INPLACE -r -e '/^[-.A-Za-z0-9_]+[ ]*=[ ]*".*"$/ {
s/^([-.A-Za-z0-9_]+)[ ]*=[ ]*"/\1=/
s/"$//
s/\\"/"/g
}' ./options/locale/*.ini
# * if the escaped line is incomplete like `key="...` or `key=..."`, quote it with backticks
# * eg: key="... => key=`"...`
# * eg: key=..." => key=`..."`
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*(".*[^"])$/\1=`\2`/' ./options/locale/*.ini
$SED_INPLACE -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*([^"].*")$/\1=`\2`/' ./options/locale/*.ini
# Remove translation under 25% of en_us # Remove translation under 25% of en_us
baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1) baselines=$(cat "./options/locale_en-US.json" | wc -l)
baselines=$((baselines / 4)) baselines=$((baselines / 4))
for filename in ./options/locale/*.ini; do for filename in ./options/locale/*.json; do
lines=$(wc -l "$filename" | cut -d" " -f1) lines=$(cat "$filename" | wc -l)
if [ $lines -lt $baselines ]; then if [ "$lines" -lt "$baselines" ]; then
echo "Removing $filename: $lines/$baselines" echo "Removing $filename: $lines/$baselines"
rm "$filename" rm "$filename"
fi fi
done done
mv ./options/locale_en-US.ini ./options/locale/ mv ./options/locale_en-US.json ./options/locale/
+7 -6
View File
@@ -13,17 +13,18 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func newActionsCommand() *cli.Command {
// CmdActions represents the available actions sub-commands. return &cli.Command{
CmdActions = &cli.Command{
Name: "actions", Name: "actions",
Usage: "Manage Gitea Actions", Usage: "Manage Gitea Actions",
Commands: []*cli.Command{ Commands: []*cli.Command{
subcmdActionsGenRunnerToken, newActionsGenerateRunnerTokenCommand(),
}, },
} }
}
subcmdActionsGenRunnerToken = &cli.Command{ func newActionsGenerateRunnerTokenCommand() *cli.Command {
return &cli.Command{
Name: "generate-runner-token", Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server", Usage: "Generate a new token for a runner to use to register with the server",
Action: runGenerateActionsRunnerToken, Action: runGenerateActionsRunnerToken,
@@ -37,7 +38,7 @@ var (
}, },
}, },
} }
) }
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
setting.MustInstalled() setting.MustInstalled()
+27 -20
View File
@@ -18,36 +18,41 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func newAdminCommand() *cli.Command {
// CmdAdmin represents the available admin sub-command. return &cli.Command{
CmdAdmin = &cli.Command{
Name: "admin", Name: "admin",
Usage: "Perform common administrative operations", Usage: "Perform common administrative operations",
Commands: []*cli.Command{ Commands: []*cli.Command{
subcmdUser, newUserCommand(),
subcmdRepoSyncReleases, newRepoSyncReleasesCommand(),
subcmdRegenerate, newRegenerateCommand(),
subcmdAuth, newAuthCommand(),
subcmdSendMail, newSendMailCommand(),
}, },
} }
}
subcmdRepoSyncReleases = &cli.Command{ func newRepoSyncReleasesCommand() *cli.Command {
return &cli.Command{
Name: "repo-sync-releases", Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags", Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases, Action: runRepoSyncReleases,
} }
}
subcmdRegenerate = &cli.Command{ func newRegenerateCommand() *cli.Command {
return &cli.Command{
Name: "regenerate", Name: "regenerate",
Usage: "Regenerate specific files", Usage: "Regenerate specific files",
Commands: []*cli.Command{ Commands: []*cli.Command{
microcmdRegenHooks, newRegenerateHooksCommand(),
microcmdRegenKeys, newRegenerateKeysCommand(),
}, },
} }
}
subcmdAuth = &cli.Command{ func newAuthCommand() *cli.Command {
return &cli.Command{
Name: "auth", Name: "auth",
Usage: "Modify external auth providers", Usage: "Modify external auth providers",
Commands: []*cli.Command{ Commands: []*cli.Command{
@@ -59,12 +64,14 @@ var (
microcmdAuthUpdateLdapSimpleAuth(), microcmdAuthUpdateLdapSimpleAuth(),
microcmdAuthAddSMTP(), microcmdAuthAddSMTP(),
microcmdAuthUpdateSMTP(), microcmdAuthUpdateSMTP(),
microcmdAuthList, newAuthListCommand(),
microcmdAuthDelete, newAuthDeleteCommand(),
}, },
} }
}
subcmdSendMail = &cli.Command{ func newSendMailCommand() *cli.Command {
return &cli.Command{
Name: "sendmail", Name: "sendmail",
Usage: "Send a message to all users", Usage: "Send a message to all users",
Action: runSendMail, Action: runSendMail,
@@ -86,7 +93,7 @@ var (
}, },
}, },
} }
) }
func idFlag() *cli.Int64Flag { func idFlag() *cli.Int64Flag {
return &cli.Int64Flag{ return &cli.Int64Flag{
@@ -121,7 +128,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
} }
log.Trace("Processing next %d repos of %d", len(repos), count) log.Trace("Processing next %d repos of %d", len(repos), count)
for _, repo := range repos { for _, repo := range repos {
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath()) log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RelativePath())
gitRepo, err := gitrepo.OpenRepository(ctx, repo) gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil { if err != nil {
log.Warn("OpenRepository: %v", err) log.Warn("OpenRepository: %v", err)
@@ -134,7 +141,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
} }
log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum) log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { if _, err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
log.Warn(" SyncReleasesWithTags: %v", err) log.Warn(" SyncReleasesWithTags: %v", err)
gitRepo.Close() gitRepo.Close()
continue continue
@@ -147,7 +154,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
continue continue
} }
log.Trace(" repo %s releases synchronized to tags: from %d to %d", log.Trace("repo %s releases synchronized to tags: from %d to %d",
repo.FullName(), oldnum, count) repo.FullName(), oldnum, count)
gitRepo.Close() gitRepo.Close()
} }
+7 -4
View File
@@ -17,14 +17,17 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func newAuthDeleteCommand() *cli.Command {
microcmdAuthDelete = &cli.Command{ return &cli.Command{
Name: "delete", Name: "delete",
Usage: "Delete specific auth source", Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag()}, Flags: []cli.Flag{idFlag()},
Action: runDeleteAuth, Action: runDeleteAuth,
} }
microcmdAuthList = &cli.Command{ }
func newAuthListCommand() *cli.Command {
return &cli.Command{
Name: "list", Name: "list",
Usage: "List auth sources", Usage: "List auth sources",
Action: runListAuth, Action: runListAuth,
@@ -55,7 +58,7 @@ var (
}, },
}, },
} }
) }
func runListAuth(ctx context.Context, c *cli.Command) error { func runListAuth(ctx context.Context, c *cli.Command) error {
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
@@ -94,6 +94,10 @@ func commonLdapCLIFlags() []cli.Flag {
Name: "public-ssh-key-attribute", Name: "public-ssh-key-attribute",
Usage: "The attribute of the users LDAP record containing the users public ssh key.", Usage: "The attribute of the users LDAP record containing the users public ssh key.",
}, },
&cli.BoolFlag{
Name: "ssh-keys-are-verified",
Usage: "Set to true to automatically flag SSH keys in LDAP as verified.",
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-local-2fa", Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source", Usage: "Set to true to skip local 2fa for users authenticated by this source",
@@ -294,6 +298,9 @@ func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("public-ssh-key-attribute") { if c.IsSet("public-ssh-key-attribute") {
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
} }
if c.IsSet("ssh-keys-are-verified") {
config.SSHKeysAreVerified = c.Bool("ssh-keys-are-verified")
}
if c.IsSet("avatar-attribute") { if c.IsSet("avatar-attribute") {
config.AttributeAvatar = c.String("avatar-attribute") config.AttributeAvatar = c.String("avatar-attribute")
} }
@@ -233,7 +233,7 @@ func TestAddLdapBindDn(t *testing.T) {
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
assert.FailNow(t, "getAuthSourceByID called", "case %d: should not call getAuthSourceByID", n) assert.FailNow(t, "getAuthSourceByID called", "case %d: should not call getAuthSourceByID", n)
return nil, nil return nil, nil //nolint:nilnil // mock function covering improper behavior
}, },
} }
@@ -463,7 +463,7 @@ func TestAddLdapSimpleAuth(t *testing.T) {
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
assert.FailNow(t, "getAuthSourceById called", "case %d: should not call getAuthSourceByID", n) assert.FailNow(t, "getAuthSourceById called", "case %d: should not call getAuthSourceByID", n)
return nil, nil return nil, nil //nolint:nilnil // mock function covering improper behavior
}, },
} }
+6 -4
View File
@@ -13,19 +13,21 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func newRegenerateHooksCommand() *cli.Command {
microcmdRegenHooks = &cli.Command{ return &cli.Command{
Name: "hooks", Name: "hooks",
Usage: "Regenerate git-hooks", Usage: "Regenerate git-hooks",
Action: runRegenerateHooks, Action: runRegenerateHooks,
} }
}
microcmdRegenKeys = &cli.Command{ func newRegenerateKeysCommand() *cli.Command {
return &cli.Command{
Name: "keys", Name: "keys",
Usage: "Regenerate authorized_keys file", Usage: "Regenerate authorized_keys file",
Action: runRegenerateKeys, Action: runRegenerateKeys,
} }
) }
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
+5 -3
View File
@@ -7,15 +7,17 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var subcmdUser = &cli.Command{ func newUserCommand() *cli.Command {
return &cli.Command{
Name: "user", Name: "user",
Usage: "Modify users", Usage: "Modify users",
Commands: []*cli.Command{ Commands: []*cli.Command{
microcmdUserCreate(), microcmdUserCreate(),
microcmdUserList, newUserListCommand(),
microcmdUserChangePassword(), microcmdUserChangePassword(),
microcmdUserDelete(), microcmdUserDelete(),
microcmdUserGenerateAccessToken, newUserGenerateAccessTokenCommand(),
microcmdUserMustChangePassword(), microcmdUserMustChangePassword(),
}, },
}
} }
@@ -151,6 +151,7 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("generated random password is '%s'\n", password) fmt.Printf("generated random password is '%s'\n", password)
} else if userType == user_model.UserTypeIndividual { } else if userType == user_model.UserTypeIndividual {
return errors.New("must set either password or random-password flag") return errors.New("must set either password or random-password flag")
@@ -14,7 +14,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var microcmdUserGenerateAccessToken = &cli.Command{ func newUserGenerateAccessTokenCommand() *cli.Command {
return &cli.Command{
Name: "generate-access-token", Name: "generate-access-token",
Usage: "Generate an access token for a specific user", Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{ Flags: []cli.Flag{
@@ -40,6 +41,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
}, },
}, },
Action: runGenerateAccessToken, Action: runGenerateAccessToken,
}
} }
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
+3 -1
View File
@@ -14,7 +14,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var microcmdUserList = &cli.Command{ func newUserListCommand() *cli.Command {
return &cli.Command{
Name: "list", Name: "list",
Usage: "List users", Usage: "List users",
Action: runListUsers, Action: runListUsers,
@@ -24,6 +25,7 @@ var microcmdUserList = &cli.Command{
Usage: "List only admin users", Usage: "List only admin users",
}, },
}, },
}
} }
func runListUsers(ctx context.Context, c *cli.Command) error { func runListUsers(ctx context.Context, c *cli.Command) error {
@@ -58,6 +58,7 @@ func runMustChangePassword(ctx context.Context, c *cli.Command) error {
return err return err
} }
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword) fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
return nil return nil
} }
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func cmdConfig() *cli.Command {
subcmdConfigEditIni := &cli.Command{
Name: "edit-ini",
Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.",
Description: `
Help users to edit the Gitea configuration INI file.
# Keep Specified Keys
If you need to re-create the configuration file with only a subset of keys,
you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag.
For example, if a helm chart needs to reset the settings and only keep SECRET_KEY,
it can use a template file (only keys take effect, values are ignored):
[security]
SECRET_KEY=
$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini
# Map Environment Variables to INI Configuration
Environment variables of the form "GITEA__section_name__KEY_NAME"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value as provided.
Environment variables of the form "GITEA__section_name__KEY_NAME__FILE"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value loaded from the specified file.
Environment variable keys can only contain characters "0-9A-Z_",
if a section or key name contains dot ".", it needs to be escaped as _0x2E_.
For example, to apply this config:
[git.config]
foo.bar=val
$ export GITEA__git_0x2E_config__foo_0x2E_bar=val
# Put All Together
$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini}
`,
Flags: []cli.Flag{
// "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper
// "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file
&cli.BoolFlag{
Name: "in-place",
Usage: "Output to the same config file as input. This flag will be ignored if --out is set.",
},
&cli.StringFlag{
Name: "config-keep-keys",
Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.",
},
&cli.BoolFlag{
Name: "apply-env",
Usage: "Apply all GITEA__* variables from the environment to the config.",
},
&cli.StringFlag{
Name: "out",
Usage: "Destination config file to write to.",
},
},
Action: runConfigEditIni,
}
return &cli.Command{
Name: "config",
Usage: "Manage Gitea configuration",
Commands: []*cli.Command{
subcmdConfigEditIni,
},
}
}
func runConfigEditIni(_ context.Context, c *cli.Command) error {
// the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...)
// don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly
if !c.IsSet("config") {
return errors.New("flag is required but not set: --config")
}
configFileIn := c.String("config")
cfgIn, err := setting.NewConfigProviderFromFile(configFileIn)
if err != nil {
return fmt.Errorf("failed to load config file %q: %v", configFileIn, err)
}
// determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file
inPlace := c.Bool("in-place")
configFileOut := c.String("out")
if configFileOut == "" {
if !inPlace {
return errors.New("either --in-place or --out must be specified")
}
configFileOut = configFileIn // in-place edit
}
needWriteOut := configFileOut != configFileIn
cfgOut := cfgIn
configKeepKeys := c.String("config-keep-keys")
if configKeepKeys != "" {
needWriteOut = true
cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys)
if err != nil {
return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err)
}
for _, secOut := range cfgOut.Sections() {
for _, keyOut := range secOut.Keys() {
secIn := cfgIn.Section(secOut.Name())
keyIn := setting.ConfigSectionKey(secIn, keyOut.Name())
if keyIn != nil {
keyOut.SetValue(keyIn.String())
} else {
secOut.DeleteKey(keyOut.Name())
}
}
if len(secOut.Keys()) == 0 {
cfgOut.DeleteSection(secOut.Name())
}
}
}
if c.Bool("apply-env") {
if setting.EnvironmentToConfig(cfgOut, env) {
needWriteOut = true
}
}
if needWriteOut {
err = cfgOut.SaveTo(configFileOut)
if err != nil {
return err
}
}
return nil
}
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestConfigEdit(t *testing.T) {
tmpDir := t.TempDir()
configOld := tmpDir + "/app-old.ini"
configTemplate := tmpDir + "/app-template.ini"
_ = os.WriteFile(configOld, []byte(`
[sec]
k1=v1
k2=v2
`), os.ModePerm)
_ = os.WriteFile(configTemplate, []byte(`
[sec]
k1=in-template
[sec2]
k3=v3
`), os.ModePerm)
t.Setenv("GITEA__EnV__KeY", "val")
t.Run("OutputToNewWithEnv", func(t *testing.T) {
configNew := tmpDir + "/app-new.ini"
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
"./gitea", "--config", configOld,
"config", "edit-ini",
"--apply-env",
"--config-keep-keys", configTemplate,
"--out", configNew,
})
require.NoError(t, err)
// "k1" old value is kept because its key is in the template
// "k2" is removed because it isn't in the template
// "k3" isn't in new config because it isn't in the old config
// [env] is applied from environment variable
data, _ := os.ReadFile(configNew)
require.Equal(t, `[sec]
k1 = v1
[env]
KeY = val
`, string(data))
})
t.Run("OutputToExisting(environment-to-ini)", func(t *testing.T) {
// the legacy "environment-to-ini" (now a wrapper script) behavior:
// if no "--out", then "--in-place" must be used to overwrite the existing "--config" file
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
"./gitea", "config", "edit-ini",
"--apply-env",
"--config", configOld,
})
require.ErrorContains(t, err, "either --in-place or --out must be specified")
// simulate the "environment-to-ini" behavior with "--in-place"
err = NewMainApp(AppVersion{}).Run(t.Context(), []string{
"./gitea", "config", "edit-ini",
"--in-place",
"--apply-env",
"--config", configOld,
})
require.NoError(t, err)
data, _ := os.ReadFile(configOld)
require.Equal(t, `[sec]
k1 = v1
k2 = v2
[env]
KeY = val
`, string(data))
})
}
+3 -2
View File
@@ -13,8 +13,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdDocs represents the available docs sub-command. func newDocsCommand() *cli.Command {
var CmdDocs = &cli.Command{ return &cli.Command{
Name: "docs", Name: "docs",
Usage: "Output CLI documentation", Usage: "Output CLI documentation",
Description: "A command to output Gitea's CLI documentation, optionally to a file.", Description: "A command to output Gitea's CLI documentation, optionally to a file.",
@@ -30,6 +30,7 @@ var CmdDocs = &cli.Command{
Usage: "Path to output to instead of stdout (will overwrite if exists)", Usage: "Path to output to instead of stdout (will overwrite if exists)",
}, },
}, },
}
} }
func runDocs(_ context.Context, cmd *cli.Command) error { func runDocs(_ context.Context, cmd *cli.Command) error {
+12 -8
View File
@@ -24,20 +24,21 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// CmdDoctor represents the available doctor sub-command. func newDoctorCommand() *cli.Command {
var CmdDoctor = &cli.Command{ return &cli.Command{
Name: "doctor", Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables", Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Commands: []*cli.Command{ Commands: []*cli.Command{
cmdDoctorCheck, newDoctorCheckCommand(),
cmdRecreateTable, newRecreateTableCommand(),
cmdDoctorConvert, newDoctorConvertCommand(),
}, },
}
} }
var cmdDoctorCheck = &cli.Command{ func newDoctorCheckCommand() *cli.Command {
return &cli.Command{
Name: "check", Name: "check",
Usage: "Diagnose and optionally fix problems", Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
@@ -73,9 +74,11 @@ var cmdDoctorCheck = &cli.Command{
Usage: "Use color for outputted information", Usage: "Use color for outputted information",
}, },
}, },
}
} }
var cmdRecreateTable = &cli.Command{ func newRecreateTableCommand() *cli.Command {
return &cli.Command{
Name: "recreate-table", Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.", Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
@@ -91,6 +94,7 @@ This command will cause Xorm to recreate tables, copying over the data and delet
You should back-up your database before doing this and ensure that your database is up-to-date first.`, You should back-up your database before doing this and ensure that your database is up-to-date first.`,
Action: runRecreateTable, Action: runRecreateTable,
}
} }
func runRecreateTable(ctx context.Context, cmd *cli.Command) error { func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
+3 -2
View File
@@ -14,12 +14,13 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// cmdDoctorConvert represents the available convert sub-command. func newDoctorConvertCommand() *cli.Command {
var cmdDoctorConvert = &cli.Command{ return &cli.Command{
Name: "convert", Name: "convert",
Usage: "Convert the database", Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar", Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",
Action: runDoctorConvert, Action: runDoctorConvert,
}
} }
func runDoctorConvert(ctx context.Context, cmd *cli.Command) error { func runDoctorConvert(ctx context.Context, cmd *cli.Command) error {
+1 -1
View File
@@ -23,7 +23,7 @@ func TestDoctorRun(t *testing.T) {
SkipDatabaseInitialization: true, SkipDatabaseInitialization: true,
}) })
app := &cli.Command{ app := &cli.Command{
Commands: []*cli.Command{cmdDoctorCheck}, Commands: []*cli.Command{newDoctorCheckCommand()},
} }
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"}) err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
assert.NoError(t, err) assert.NoError(t, err)
+3 -2
View File
@@ -23,8 +23,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdDump represents the available dump sub-command. func newDumpCommand() *cli.Command {
var CmdDump = &cli.Command{ return &cli.Command{
Name: "dump", Name: "dump",
Usage: "Dump Gitea files and database", Usage: "Dump Gitea files and database",
Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`, Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`,
@@ -95,6 +95,7 @@ var CmdDump = &cli.Command{
Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "), Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "),
}, },
}, },
}
} }
func fatal(format string, args ...any) { func fatal(format string, args ...any) {
+3 -2
View File
@@ -22,8 +22,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdDumpRepository represents the available dump repository sub-command. func newDumpRepositoryCommand() *cli.Command {
var CmdDumpRepository = &cli.Command{ return &cli.Command{
Name: "dump-repo", Name: "dump-repo",
Usage: "Dump the repository from git/github/gitea/gitlab", Usage: "Dump the repository from git/github/gitea/gitlab",
Description: "This is a command for dumping the repository data.", Description: "This is a command for dumping the repository data.",
@@ -77,6 +77,7 @@ var CmdDumpRepository = &cli.Command{
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
}, },
}, },
}
} }
func runDumpRepository(ctx context.Context, cmd *cli.Command) error { func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
+17 -12
View File
@@ -23,20 +23,23 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdEmbedded represents the available extract sub-command. var matchedAssetFiles []assetFile
var (
CmdEmbedded = &cli.Command{ func newEmbeddedCommand() *cli.Command {
return &cli.Command{
Name: "embedded", Name: "embedded",
Usage: "Extract embedded resources", Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images", Description: "A command for extracting embedded resources, like templates and images",
Commands: []*cli.Command{ Commands: []*cli.Command{
subcmdList, newEmbeddedListCommand(),
subcmdView, newEmbeddedViewCommand(),
subcmdExtract, newEmbeddedExtractCommand(),
}, },
} }
}
subcmdList = &cli.Command{ func newEmbeddedListCommand() *cli.Command {
return &cli.Command{
Name: "list", Name: "list",
Usage: "List files matching the given pattern", Usage: "List files matching the given pattern",
Action: runList, Action: runList,
@@ -48,8 +51,10 @@ var (
}, },
}, },
} }
}
subcmdView = &cli.Command{ func newEmbeddedViewCommand() *cli.Command {
return &cli.Command{
Name: "view", Name: "view",
Usage: "View a file matching the given pattern", Usage: "View a file matching the given pattern",
Action: runView, Action: runView,
@@ -61,8 +66,10 @@ var (
}, },
}, },
} }
}
subcmdExtract = &cli.Command{ func newEmbeddedExtractCommand() *cli.Command {
return &cli.Command{
Name: "extract", Name: "extract",
Usage: "Extract resources", Usage: "Extract resources",
Action: runExtract, Action: runExtract,
@@ -91,9 +98,7 @@ var (
}, },
}, },
} }
}
matchedAssetFiles []assetFile
)
type assetFile struct { type assetFile struct {
fs *assetfs.LayeredFS fs *assetfs.LayeredFS
+21 -17
View File
@@ -15,45 +15,52 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func newGenerateCommand() *cli.Command {
// CmdGenerate represents the available generate sub-command. return &cli.Command{
CmdGenerate = &cli.Command{
Name: "generate", Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens", Usage: "Generate Gitea's secrets/keys/tokens",
Commands: []*cli.Command{ Commands: []*cli.Command{
subcmdSecret, newGenerateSecretCommand(),
}, },
} }
}
subcmdSecret = &cli.Command{ func newGenerateSecretCommand() *cli.Command {
return &cli.Command{
Name: "secret", Name: "secret",
Usage: "Generate a secret token", Usage: "Generate a secret token",
Commands: []*cli.Command{ Commands: []*cli.Command{
microcmdGenerateInternalToken, newGenerateInternalTokenCommand(),
microcmdGenerateLfsJwtSecret, newGenerateLfsJWTSecretCommand(),
microcmdGenerateSecretKey, newGenerateSecretKeyCommand(),
}, },
} }
}
microcmdGenerateInternalToken = &cli.Command{ func newGenerateInternalTokenCommand() *cli.Command {
return &cli.Command{
Name: "INTERNAL_TOKEN", Name: "INTERNAL_TOKEN",
Usage: "Generate a new INTERNAL_TOKEN", Usage: "Generate a new INTERNAL_TOKEN",
Action: runGenerateInternalToken, Action: runGenerateInternalToken,
} }
}
microcmdGenerateLfsJwtSecret = &cli.Command{ func newGenerateLfsJWTSecretCommand() *cli.Command {
return &cli.Command{
Name: "JWT_SECRET", Name: "JWT_SECRET",
Aliases: []string{"LFS_JWT_SECRET"}, Aliases: []string{"LFS_JWT_SECRET"},
Usage: "Generate a new JWT_SECRET", Usage: "Generate a new JWT_SECRET",
Action: runGenerateLfsJwtSecret, Action: runGenerateLfsJwtSecret,
} }
}
microcmdGenerateSecretKey = &cli.Command{ func newGenerateSecretKeyCommand() *cli.Command {
return &cli.Command{
Name: "SECRET_KEY", Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY", Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey, Action: runGenerateSecretKey,
} }
) }
func runGenerateInternalToken(_ context.Context, c *cli.Command) error { func runGenerateInternalToken(_ context.Context, c *cli.Command) error {
internalToken, err := generate.NewInternalToken() internalToken, err := generate.NewInternalToken()
@@ -71,11 +78,7 @@ func runGenerateInternalToken(_ context.Context, c *cli.Command) error {
} }
func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error { func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error {
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() _, jwtSecretBase64 := generate.NewJwtSecretWithBase64()
if err != nil {
return err
}
fmt.Printf("%s", jwtSecretBase64) fmt.Printf("%s", jwtSecretBase64)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
@@ -91,6 +94,7 @@ func runGenerateSecretKey(_ context.Context, c *cli.Command) error {
return err return err
} }
// codeql[disable-next-line=go/clear-text-logging]
fmt.Printf("%s", secretKey) fmt.Printf("%s", secretKey)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
+43 -21
View File
@@ -28,23 +28,24 @@ const (
hookBatchSize = 500 hookBatchSize = 500
) )
var ( func newHookCommand() *cli.Command {
// CmdHook represents the available hooks sub-command. return &cli.Command{
CmdHook = &cli.Command{
Name: "hook", Name: "hook",
Usage: "(internal) Should only be called by Git", Usage: "(internal) Should only be called by Git",
Hidden: true, // internal commands shouldn't be visible Hidden: true, // internal commands shouldn't be visible
Description: "Delegate commands to corresponding Git hooks", Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Commands: []*cli.Command{ Commands: []*cli.Command{
subcmdHookPreReceive, newHookPreReceiveCommand(),
subcmdHookUpdate, newHookUpdateCommand(),
subcmdHookPostReceive, newHookPostReceiveCommand(),
subcmdHookProcReceive, newHookProcReceiveCommand(),
}, },
} }
}
subcmdHookPreReceive = &cli.Command{ func newHookPreReceiveCommand() *cli.Command {
return &cli.Command{
Name: "pre-receive", Name: "pre-receive",
Usage: "Delegate pre-receive Git hook", Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@@ -55,7 +56,10 @@ var (
}, },
}, },
} }
subcmdHookUpdate = &cli.Command{ }
func newHookUpdateCommand() *cli.Command {
return &cli.Command{
Name: "update", Name: "update",
Usage: "Delegate update Git hook", Usage: "Delegate update Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@@ -66,7 +70,10 @@ var (
}, },
}, },
} }
subcmdHookPostReceive = &cli.Command{ }
func newHookPostReceiveCommand() *cli.Command {
return &cli.Command{
Name: "post-receive", Name: "post-receive",
Usage: "Delegate post-receive Git hook", Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@@ -77,8 +84,11 @@ var (
}, },
}, },
} }
// Note: new hook since git 2.29 }
subcmdHookProcReceive = &cli.Command{
// Note: new hook since git 2.29
func newHookProcReceiveCommand() *cli.Command {
return &cli.Command{
Name: "proc-receive", Name: "proc-receive",
Usage: "Delegate proc-receive Git hook", Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
@@ -89,7 +99,7 @@ var (
}, },
}, },
} }
) }
type delayWriter struct { type delayWriter struct {
internal io.Writer internal io.Writer
@@ -194,7 +204,7 @@ Gitea or set your environment appropriately.`, "")
userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64) deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
actionPerm, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionPerm), 10, 64) actionsTaskID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionsTaskID), 10, 64)
hookOptions := private.HookOptions{ hookOptions := private.HookOptions{
UserID: userID, UserID: userID,
@@ -204,7 +214,8 @@ Gitea or set your environment appropriately.`, "")
GitPushOptions: pushOptions(), GitPushOptions: pushOptions(),
PullRequestID: prID, PullRequestID: prID,
DeployKeyID: deployKeyID, DeployKeyID: deployKeyID,
ActionPerm: int(actionPerm), ActionsTaskID: actionsTaskID,
IsWiki: isWiki,
} }
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
@@ -275,6 +286,9 @@ Gitea or set your environment appropriately.`, "")
lastline = 0 lastline = 0
} }
} }
if err := scanner.Err(); err != nil {
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
}
if count > 0 { if count > 0 {
hookOptions.OldCommitIDs = oldCommitIDs[:count] hookOptions.OldCommitIDs = oldCommitIDs[:count]
@@ -318,7 +332,7 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what // First of all run update-server-info no matter what
if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { if err := gitcmd.NewCommand("update-server-info").RunWithStderr(ctx); err != nil {
return fmt.Errorf("failed to call 'git update-server-info': %w", err) return fmt.Errorf("failed to call 'git update-server-info': %w", err)
} }
@@ -366,6 +380,7 @@ Gitea or set your environment appropriately.`, "")
GitPushOptions: pushOptions(), GitPushOptions: pushOptions(),
PullRequestID: prID, PullRequestID: prID,
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)), PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
IsWiki: isWiki,
} }
oldCommitIDs := make([]string, hookBatchSize) oldCommitIDs := make([]string, hookBatchSize)
newCommitIDs := make([]string, hookBatchSize) newCommitIDs := make([]string, hookBatchSize)
@@ -413,6 +428,11 @@ Gitea or set your environment appropriately.`, "")
count = 0 count = 0
} }
} }
if err := scanner.Err(); err != nil {
_ = dWriter.Close()
hookPrintResults(results)
return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err)
}
if count == 0 { if count == 0 {
if wasEmpty && masterPushed { if wasEmpty && masterPushed {
@@ -513,6 +533,7 @@ Gitea or set your environment appropriately.`, "")
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
repoUser := os.Getenv(repo_module.EnvRepoUsername) repoUser := os.Getenv(repo_module.EnvRepoUsername)
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
repoName := os.Getenv(repo_module.EnvRepoName) repoName := os.Getenv(repo_module.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
pusherName := os.Getenv(repo_module.EnvPusherName) pusherName := os.Getenv(repo_module.EnvPusherName)
@@ -590,14 +611,15 @@ Gitea or set your environment appropriately.`, "")
UserName: pusherName, UserName: pusherName,
UserID: pusherID, UserID: pusherID,
GitPushOptions: make(map[string]string), GitPushOptions: make(map[string]string),
IsWiki: isWiki,
} }
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize) hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize) hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.RefFullNames = make([]git.RefName, 0, hookBatchSize) hookOptions.RefFullNames = make([]git.RefName, 0, hookBatchSize)
for { for {
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed // note: pktLineTypeUnknown means pktLineTypeFlush and pktLineTypeData all allowed
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow) rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil { if err != nil {
return err return err
} }
@@ -616,7 +638,7 @@ Gitea or set your environment appropriately.`, "")
if hasPushOptions { if hasPushOptions {
for { for {
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow) rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
if err != nil { if err != nil {
return err return err
} }
@@ -713,8 +735,8 @@ Gitea or set your environment appropriately.`, "")
type pktLineType int64 type pktLineType int64
const ( const (
// UnKnow type // Unknown type
pktLineTypeUnknow pktLineType = 0 pktLineTypeUnknown pktLineType = 0
// flush-pkt "0000" // flush-pkt "0000"
pktLineTypeFlush pktLineType = iota pktLineTypeFlush pktLineType = iota
// data line // data line
+4 -2
View File
@@ -15,8 +15,9 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdKeys represents the available keys sub-command // NewKeysCommand returns the internal SSH key lookup sub-command.
var CmdKeys = &cli.Command{ func NewKeysCommand() *cli.Command {
return &cli.Command{
Name: "keys", Name: "keys",
Usage: "(internal) Should only be called by SSH server", Usage: "(internal) Should only be called by SSH server",
Hidden: true, // internal commands shouldn't be visible Hidden: true, // internal commands shouldn't be visible
@@ -49,6 +50,7 @@ var CmdKeys = &cli.Command{
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
}, },
}, },
}
} }
func runKeys(ctx context.Context, c *cli.Command) error { func runKeys(ctx context.Context, c *cli.Command) error {
+2 -2
View File
@@ -17,10 +17,10 @@ func runSendMail(ctx context.Context, c *cli.Command) error {
setting.MustInstalled() setting.MustInstalled()
subject := c.String("title") subject := c.String("title")
confirmSkiped := c.Bool("force") confirmSkipped := c.Bool("force")
body := c.String("content") body := c.String("content")
if !confirmSkiped { if !confirmSkipped {
if len(body) == 0 { if len(body) == 0 {
fmt.Print("warning: Content is empty") fmt.Print("warning: Content is empty")
} }
+20 -18
View File
@@ -112,34 +112,36 @@ func NewMainApp(appVer AppVersion) *cli.Command {
Usage: "Set custom path (defaults to '{WorkPath}/custom')", Usage: "Set custom path (defaults to '{WorkPath}/custom')",
}, },
} }
webCmd := newWebCommand()
// these sub-commands need to use a config file // these sub-commands need to use a config file
subCmdWithConfig := []*cli.Command{ subCmdWithConfig := []*cli.Command{
CmdWeb, webCmd,
CmdServ, newServCommand(),
CmdHook, newHookCommand(),
CmdKeys, NewKeysCommand(),
CmdDump, newDumpCommand(),
CmdAdmin, newAdminCommand(),
CmdMigrate, newMigrateCommand(),
CmdDoctor, newDoctorCommand(),
CmdManager, newManagerCommand(),
CmdEmbedded, newEmbeddedCommand(),
CmdMigrateStorage, newMigrateStorageCommand(),
CmdDumpRepository, newDumpRepositoryCommand(),
CmdRestoreRepository, newRestoreRepositoryCommand(),
CmdActions, newActionsCommand(),
} }
// these sub-commands do not need the config file, and they do not depend on any path or environment variable. // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{ subCmdStandalone := []*cli.Command{
cmdConfig(),
cmdCert(), cmdCert(),
CmdGenerate, newGenerateCommand(),
CmdDocs, newDocsCommand(),
} }
// TODO: we should eventually drop the default command, // TODO: we should eventually drop the default command,
// but not sure whether it would break Windows users who used to double-click the EXE to run. // but not sure whether it would break Windows users who used to double-click the EXE to run.
app.DefaultCommand = CmdWeb.Name app.DefaultCommand = webCmd.Name
app.Before = PrepareConsoleLoggerLevel(log.INFO) app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig { for i := range subCmdWithConfig {
@@ -148,7 +150,7 @@ func NewMainApp(appVer AppVersion) *cli.Command {
app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...) app.Commands = append(app.Commands, subCmdStandalone...)
setting.InitGiteaEnvVars() setting.UnsetUnnecessaryEnvVars()
return app return app
} }
+5 -1
View File
@@ -157,6 +157,7 @@ func TestCliCmd(t *testing.T) {
for _, c := range cases { for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) { t.Run(c.cmd, func(t *testing.T) {
defer test.MockVariableValue(&setting.InstallLock, false)()
app := newTestApp(cli.Command{ app := newTestApp(cli.Command{
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
@@ -170,7 +171,10 @@ func TestCliCmd(t *testing.T) {
r, err := runTestApp(app, args...) r, err := runTestApp(app, args...)
assert.NoError(t, err, c.cmd) assert.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd) assert.NotEmpty(t, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd) if !assert.Contains(t, r.Stdout, c.exp, c.cmd) {
t.Log("Full output:\n" + r.Stdout)
t.Log("Expected:\n" + c.exp)
}
}) })
} }
} }
+29 -15
View File
@@ -13,22 +13,24 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func newManagerCommand() *cli.Command {
// CmdManager represents the manager command return &cli.Command{
CmdManager = &cli.Command{
Name: "manager", Name: "manager",
Usage: "Manage the running gitea process", Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process", Description: "This is a command for managing the running gitea process",
Commands: []*cli.Command{ Commands: []*cli.Command{
subcmdShutdown, newShutdownCommand(),
subcmdRestart, newRestartCommand(),
subcmdReloadTemplates, newReloadTemplatesCommand(),
subcmdFlushQueues, newFlushQueuesCommand(),
subcmdLogging, newLoggingCommand(),
subCmdProcesses, newProcessesCommand(),
}, },
} }
subcmdShutdown = &cli.Command{ }
func newShutdownCommand() *cli.Command {
return &cli.Command{
Name: "shutdown", Name: "shutdown",
Usage: "Gracefully shutdown the running process", Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{ Flags: []cli.Flag{
@@ -38,7 +40,10 @@ var (
}, },
Action: runShutdown, Action: runShutdown,
} }
subcmdRestart = &cli.Command{ }
func newRestartCommand() *cli.Command {
return &cli.Command{
Name: "restart", Name: "restart",
Usage: "Gracefully restart the running process - (not implemented for windows servers)", Usage: "Gracefully restart the running process - (not implemented for windows servers)",
Flags: []cli.Flag{ Flags: []cli.Flag{
@@ -48,7 +53,10 @@ var (
}, },
Action: runRestart, Action: runRestart,
} }
subcmdReloadTemplates = &cli.Command{ }
func newReloadTemplatesCommand() *cli.Command {
return &cli.Command{
Name: "reload-templates", Name: "reload-templates",
Usage: "Reload template files in the running process", Usage: "Reload template files in the running process",
Flags: []cli.Flag{ Flags: []cli.Flag{
@@ -58,7 +66,10 @@ var (
}, },
Action: runReloadTemplates, Action: runReloadTemplates,
} }
subcmdFlushQueues = &cli.Command{ }
func newFlushQueuesCommand() *cli.Command {
return &cli.Command{
Name: "flush-queues", Name: "flush-queues",
Usage: "Flush queues in the running process", Usage: "Flush queues in the running process",
Action: runFlushQueues, Action: runFlushQueues,
@@ -77,7 +88,10 @@ var (
}, },
}, },
} }
subCmdProcesses = &cli.Command{ }
func newProcessesCommand() *cli.Command {
return &cli.Command{
Name: "processes", Name: "processes",
Usage: "Display running processes within the current process", Usage: "Display running processes within the current process",
Action: runProcesses, Action: runProcesses,
@@ -107,7 +121,7 @@ var (
}, },
}, },
} }
) }
func runShutdown(ctx context.Context, c *cli.Command) error { func runShutdown(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
+8 -6
View File
@@ -15,8 +15,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func defaultLoggingFlags() []cli.Flag {
defaultLoggingFlags = []cli.Flag{ return []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "logger", Name: "logger",
Usage: `Logger name - will default to "default"`, Usage: `Logger name - will default to "default"`,
@@ -57,8 +57,10 @@ var (
Name: "debug", Name: "debug",
}, },
} }
}
subcmdLogging = &cli.Command{ func newLoggingCommand() *cli.Command {
return &cli.Command{
Name: "logging", Name: "logging",
Usage: "Adjust logging commands", Usage: "Adjust logging commands",
Commands: []*cli.Command{ Commands: []*cli.Command{
@@ -109,7 +111,7 @@ var (
{ {
Name: "file", Name: "file",
Usage: "Add a file logger", Usage: "Add a file logger",
Flags: append(defaultLoggingFlags, []cli.Flag{ Flags: append(defaultLoggingFlags(), []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "filename", Name: "filename",
Aliases: []string{"f"}, Aliases: []string{"f"},
@@ -150,7 +152,7 @@ var (
}, { }, {
Name: "conn", Name: "conn",
Usage: "Add a net conn logger", Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags, []cli.Flag{ Flags: append(defaultLoggingFlags(), []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "reconnect-on-message", Name: "reconnect-on-message",
Aliases: []string{"R"}, Aliases: []string{"R"},
@@ -191,7 +193,7 @@ var (
}, },
}, },
} }
) }
func runRemoveLogger(ctx context.Context, c *cli.Command) error { func runRemoveLogger(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
+3 -2
View File
@@ -14,12 +14,13 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdMigrate represents the available migrate sub-command. func newMigrateCommand() *cli.Command {
var CmdMigrate = &cli.Command{ return &cli.Command{
Name: "migrate", Name: "migrate",
Usage: "Migrate the database", Usage: "Migrate the database",
Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`, Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
Action: runMigrate, Action: runMigrate,
}
} }
func runMigrate(ctx context.Context, c *cli.Command) error { func runMigrate(ctx context.Context, c *cli.Command) error {
+4 -3
View File
@@ -25,8 +25,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdMigrateStorage represents the available migrate storage sub-command. func newMigrateStorageCommand() *cli.Command {
var CmdMigrateStorage = &cli.Command{ return &cli.Command{
Name: "migrate-storage", Name: "migrate-storage",
Usage: "Migrate the storage", Usage: "Migrate the storage",
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
@@ -36,7 +36,7 @@ var CmdMigrateStorage = &cli.Command{
Name: "type", Name: "type",
Aliases: []string{"t"}, Aliases: []string{"t"},
Value: "", Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts", Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "storage", Name: "storage",
@@ -126,6 +126,7 @@ var CmdMigrateStorage = &cli.Command{
Usage: "Azure Blob storage base path", Usage: "Azure Blob storage base path",
}, },
}, },
}
} }
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
+3 -2
View File
@@ -13,8 +13,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdRestoreRepository represents the available restore a repository sub-command. func newRestoreRepositoryCommand() *cli.Command {
var CmdRestoreRepository = &cli.Command{ return &cli.Command{
Name: "restore-repo", Name: "restore-repo",
Usage: "Restore the repository from disk", Usage: "Restore the repository from disk",
Description: "This is a command for restoring the repository data.", Description: "This is a command for restoring the repository data.",
@@ -47,6 +47,7 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
Usage: "Sanity check the content of the files before trying to load them", Usage: "Sanity check the content of the files before trying to load them",
}, },
}, },
}
} }
func runRestoreRepository(ctx context.Context, c *cli.Command) error { func runRestoreRepository(ctx context.Context, c *cli.Command) error {
+3 -2
View File
@@ -35,8 +35,8 @@ import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
// CmdServ represents the available serv sub-command. func newServCommand() *cli.Command {
var CmdServ = &cli.Command{ return &cli.Command{
Name: "serv", Name: "serv",
Usage: "(internal) Should only be called by SSH shell", Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories", Description: "Serv provides access auth for repositories",
@@ -51,6 +51,7 @@ var CmdServ = &cli.Command{
Name: "debug", Name: "debug",
}, },
}, },
}
} }
func setup(ctx context.Context, debug bool) { func setup(ctx context.Context, debug bool) {
+18 -17
View File
@@ -8,14 +8,13 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/http/pprof"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/gtprof" "code.gitea.io/gitea/modules/gtprof"
@@ -23,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/install" "code.gitea.io/gitea/routers/install"
@@ -34,8 +34,8 @@ import (
// PIDFile could be set from build tag // PIDFile could be set from build tag
var PIDFile = "/run/gitea.pid" var PIDFile = "/run/gitea.pid"
// CmdWeb represents the available web sub-command. func newWebCommand() *cli.Command {
var CmdWeb = &cli.Command{ return &cli.Command{
Name: "web", Name: "web",
Usage: "Start Gitea web server", Usage: "Start Gitea web server",
Description: `Gitea web server is the only thing you need to run, Description: `Gitea web server is the only thing you need to run,
@@ -70,6 +70,7 @@ and it takes care of all the other things for you`,
Usage: "Set initial logging to TRACE level until logging is properly set-up", Usage: "Set initial logging to TRACE level until logging is properly set-up",
}, },
}, },
}
} }
func runHTTPRedirector() { func runHTTPRedirector() {
@@ -156,7 +157,6 @@ func serveInstall(cmd *cli.Command) error {
case <-graceful.GetManager().IsShutdown(): case <-graceful.GetManager().IsShutdown():
<-graceful.GetManager().Done() <-graceful.GetManager().Done()
log.Info("PID: %d Gitea Web Finished", os.Getpid()) log.Info("PID: %d Gitea Web Finished", os.Getpid())
log.GetManager().Close()
return err return err
default: default:
} }
@@ -164,8 +164,6 @@ func serveInstall(cmd *cli.Command) error {
} }
func serveInstalled(c *cli.Command) error { func serveInstalled(c *cli.Command) error {
setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings()
setting.MustInstalled() setting.MustInstalled()
showWebStartupMessage("Prepare to run web server") showWebStartupMessage("Prepare to run web server")
@@ -231,27 +229,26 @@ func serveInstalled(c *cli.Command) error {
err := listen(webRoutes, true) err := listen(webRoutes, true)
<-graceful.GetManager().Done() <-graceful.GetManager().Done()
log.Info("PID: %d Gitea Web Finished", os.Getpid()) log.Info("PID: %d Gitea Web Finished", os.Getpid())
log.GetManager().Close()
return err return err
} }
func servePprof() { func servePprof() {
// FIXME: it shouldn't use the global DefaultServeMux, and it should use a proper context mux := http.NewServeMux()
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler()) mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
mux.Handle("/debug/fgprof", fgprof.Handler())
// FIXME: it should use a proper context
_, _, finished := process.GetManager().AddTypedContext(context.TODO(), "Web: PProf Server", process.SystemProcessType, true) _, _, finished := process.GetManager().AddTypedContext(context.TODO(), "Web: PProf Server", process.SystemProcessType, true)
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment, it's not worth introducing a configurable option for it. // The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment, it's not worth introducing a configurable option for it.
log.Info("Starting pprof server on localhost:6060") log.Info("Starting pprof server on localhost:6060")
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil)) log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", mux))
finished() finished()
} }
func runWeb(ctx context.Context, cmd *cli.Command) error { func runWeb(ctx context.Context, cmd *cli.Command) error {
defer func() {
if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
}
}()
if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid { if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid {
return fmt.Errorf("unknown command: %s", subCmdName) return fmt.Errorf("unknown command: %s", subCmdName)
} }
@@ -271,6 +268,10 @@ func runWeb(ctx context.Context, cmd *cli.Command) error {
createPIDFile(cmd.String("pid")) createPIDFile(cmd.String("pid"))
} }
// init the HTML renderer and load templates, if error happens, it will report the error immediately and exit with error log
// in dev mode, it won't exit, but watch the template files for changes
_ = templates.PageRenderer()
if !setting.InstallLock { if !setting.InstallLock {
if err := serveInstall(cmd); err != nil { if err := serveInstall(cmd); err != nil {
return err return err
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
@@ -124,8 +125,8 @@ func runACME(listenAddr string, m http.Handler) error {
defer finished() defer finished()
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) // all traffic coming into HTTP will be redirected to HTTPS automatically (LE HTTP-01 validation happens here)
err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol) err := runHTTP("tcp", net.JoinHostPort(setting.HTTPAddr, setting.PortToRedirect), "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol)
if err != nil { if err != nil {
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err) log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
} }
@@ -16,7 +16,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/google/go-github/v74/github" "github.com/google/go-github/v84/github"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -1,47 +0,0 @@
Environment To Ini
==================
Multiple docker users have requested that the Gitea docker is changed
to permit arbitrary configuration via environment variables.
Gitea needs to use an ini file for configuration because the running
environment that starts the docker may not be the same as that used
by the hooks. An ini file also gives a good default and means that
users do not have to completely provide a full environment.
With those caveats above, this command provides a generic way of
converting suitably structured environment variables into any ini
value.
To use the command is very simple just run it and the default gitea
app.ini will be rewritten to take account of the variables provided,
however there are various options to give slightly different
behavior and these can be interrogated with the `-h` option.
The environment variables should be of the form:
GITEA__SECTION_NAME__KEY_NAME
Note, SECTION_NAME in the notation above is case-insensitive.
Environment variables are usually restricted to a reduced character
set "0-9A-Z_" - in order to allow the setting of sections with
characters outside of that set, they should be escaped as following:
"_0X2E_" for "." and "_0X2D_" for "-". The entire section and key names
can be escaped as a UTF8 byte string if necessary. E.g. to configure:
"""
...
[log.console]
COLORIZE=false
STDERR=true
...
"""
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
on the configuration cheat sheet.
To build locally, run:
go build contrib/environment-to-ini/environment-to-ini.go
@@ -1,112 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package main
import (
"context"
"os"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
func main() {
app := cli.Command{}
app.Name = "environment-to-ini"
app.Usage = "Use provided environment to update configuration ini"
app.Description = `As a helper to allow docker users to update the gitea configuration
through the environment, this command allows environment variables to
be mapped to values in the ini.
Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value as provided.
Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME__FILE"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value loaded from the specified file.
Environment variables are usually restricted to a reduced character
set "0-9A-Z_" - in order to allow the setting of sections with
characters outside of that set, they should be escaped as following:
"_0X2E_" for ".". The entire section and key names can be escaped as
a UTF8 byte string if necessary. E.g. to configure:
"""
...
[log.console]
COLORIZE=false
STDERR=true
...
"""
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
on the configuration cheat sheet.`
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "custom-path",
Aliases: []string{"C"},
Value: setting.CustomPath,
Usage: "Custom path file path",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Value: setting.CustomConf,
Usage: "Custom configuration file path",
},
&cli.StringFlag{
Name: "work-path",
Aliases: []string{"w"},
Value: setting.AppWorkPath,
Usage: "Set the gitea working path",
},
&cli.StringFlag{
Name: "out",
Aliases: []string{"o"},
Value: "",
Usage: "Destination file to write to",
},
}
app.Action = runEnvironmentToIni
err := app.Run(context.Background(), os.Args)
if err != nil {
log.Fatal("Failed to run app with %s: %v", os.Args, err)
}
}
func runEnvironmentToIni(_ context.Context, c *cli.Command) error {
// the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...)
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{
WorkPath: c.String("work-path"),
CustomPath: c.String("custom-path"),
CustomConf: c.String("config"),
})
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
if err != nil {
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
}
changed := setting.EnvironmentToConfig(cfg, env)
// try to save the config file
destination := c.String("out")
if len(destination) == 0 {
destination = setting.CustomConf
}
if destination != setting.CustomConf || changed {
log.Info("Settings saved to: %q", destination)
err = cfg.SaveTo(destination)
if err != nil {
return err
}
}
return nil
}
+5 -1
View File
@@ -108,7 +108,9 @@ curl --connect-timeout 10 --silent --show-error --fail --location -O "$binurl{,.
sha256sum -c "${binname}.xz.sha256" sha256sum -c "${binname}.xz.sha256"
if [[ -z "${ignore_gpg:-}" ]]; then if [[ -z "${ignore_gpg:-}" ]]; then
require gpg require gpg
gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 # try to use curl first, it uses standard tcp 443 port and works better behind strict firewall rules
curl -fsSL --connect-timeout 10 "https://keys.openpgp.org/vks/v1/by-fingerprint/7C9E68152594688862D62AF62D9AE806EC1592E2" | gpg --import \
|| gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2
gpg --verify "${binname}.xz.asc" "${binname}.xz" || { echo 'Signature does not match'; exit 1; } gpg --verify "${binname}.xz.asc" "${binname}.xz" || { echo 'Signature does not match'; exit 1; }
fi fi
rm "${binname}".xz.{sha256,asc} rm "${binname}".xz.{sha256,asc}
@@ -127,6 +129,8 @@ echo "Creating backup in $giteahome"
giteacmd dump $backupopts giteacmd dump $backupopts
echo "Updating binary at $giteabin" echo "Updating binary at $giteabin"
cp -f "$giteabin" "$giteabin.bak" && mv -f "$binname" "$giteabin" cp -f "$giteabin" "$giteabin.bak" && mv -f "$binname" "$giteabin"
# Restore SELinux context if applicable (e.g. RHEL/Fedora)
command -v restorecon &>/dev/null && restorecon -v "$giteabin" || true
$service_start $service_start
$service_status $service_status
+3 -3
View File
@@ -4,9 +4,9 @@ base_path: "."
base_url: "https://api.crowdin.com" base_url: "https://api.crowdin.com"
preserve_hierarchy: true preserve_hierarchy: true
files: files:
- source: "/options/locale/locale_en-US.ini" - source: "/options/locale/locale_en-US.json"
translation: "/options/locale/locale_%locale%.ini" translation: "/options/locale/locale_%locale%.json"
type: "ini" type: "json"
skip_untranslated_strings: true skip_untranslated_strings: true
export_only_approved: true export_only_approved: true
update_option: "update_as_unapproved" update_option: "update_as_unapproved"
@@ -1,2 +1,8 @@
#!/bin/bash #!/bin/bash
# $1 = exit code of the run script, $2 = signal
if [ "$1" -ne 0 ]; then
# avoid immediately restarting the sshd service, which may cause CPU 100% if the error (permission, configuration) is not fixed
echo "openssh failed with exit code $1 - waiting a short delay before attempting a restart"
sleep 3
fi
exit 0 exit 0
@@ -0,0 +1,2 @@
#!/bin/bash
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@"
@@ -0,0 +1,2 @@
#!/bin/bash
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@"
+86 -87
View File
@@ -1,9 +1,9 @@
import arrayFunc from 'eslint-plugin-array-func'; import arrayFunc from 'eslint-plugin-array-func';
import comments from '@eslint-community/eslint-plugin-eslint-comments'; import comments from '@eslint-community/eslint-plugin-eslint-comments';
import deMorgan from 'eslint-plugin-de-morgan';
import github from 'eslint-plugin-github'; import github from 'eslint-plugin-github';
import globals from 'globals'; import globals from 'globals';
import importPlugin from 'eslint-plugin-import-x'; import importPlugin from 'eslint-plugin-import-x';
import noUseExtendNative from 'eslint-plugin-no-use-extend-native';
import playwright from 'eslint-plugin-playwright'; import playwright from 'eslint-plugin-playwright';
import regexp from 'eslint-plugin-regexp'; import regexp from 'eslint-plugin-regexp';
import sonarjs from 'eslint-plugin-sonarjs'; import sonarjs from 'eslint-plugin-sonarjs';
@@ -16,10 +16,22 @@ import vue from 'eslint-plugin-vue';
import vueScopedCss from 'eslint-plugin-vue-scoped-css'; import vueScopedCss from 'eslint-plugin-vue-scoped-css';
import wc from 'eslint-plugin-wc'; import wc from 'eslint-plugin-wc';
import {defineConfig, globalIgnores} from 'eslint/config'; import {defineConfig, globalIgnores} from 'eslint/config';
import type {ESLint} from 'eslint';
const jsExts = ['js', 'mjs', 'cjs'] as const; const jsExts = ['js', 'mjs', 'cjs'] as const;
const tsExts = ['ts', 'mts', 'cts'] as const; const tsExts = ['ts', 'mts', 'cts'] as const;
const restrictedSyntax = ['WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression'];
const restrictedGlobals = [
{name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
{name: 'fetch', message: 'Use `modules/fetch.ts` instead.'},
];
const restrictedProperties = [
{object: 'window', property: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
{object: 'globalThis', property: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
{object: 'window', property: 'fetch', message: 'Use `modules/fetch.ts` instead.'},
{object: 'globalThis', property: 'fetch', message: 'Use `modules/fetch.ts` instead.'},
];
export default defineConfig([ export default defineConfig([
globalIgnores([ globalIgnores([
@@ -33,10 +45,6 @@ export default defineConfig([
languageOptions: { languageOptions: {
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: 'module', sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
},
parser: typescriptParser, parser: typescriptParser,
parserOptions: { parserOptions: {
sourceType: 'module', sourceType: 'module',
@@ -49,24 +57,19 @@ export default defineConfig([
}, },
linterOptions: { linterOptions: {
reportUnusedDisableDirectives: 2, reportUnusedDisableDirectives: 2,
reportUnusedInlineConfigs: 2,
}, },
plugins: { plugins: {
'@eslint-community/eslint-comments': comments, '@eslint-community/eslint-comments': comments,
// @ts-expect-error
'@stylistic': stylistic, '@stylistic': stylistic,
'@typescript-eslint': typescriptPlugin.plugin, '@typescript-eslint': typescriptPlugin.plugin,
'array-func': arrayFunc, 'array-func': arrayFunc,
// @ts-expect-error -- https://github.com/un-ts/eslint-plugin-import-x/issues/203 'de-morgan': deMorgan,
'import-x': importPlugin, 'import-x': importPlugin as unknown as ESLint.Plugin, // https://github.com/un-ts/eslint-plugin-import-x/issues/203
'no-use-extend-native': noUseExtendNative,
// @ts-expect-error
regexp, regexp,
// @ts-expect-error
sonarjs, sonarjs,
// @ts-expect-error
unicorn, unicorn,
github, github,
// @ts-expect-error
wc, wc,
}, },
settings: { settings: {
@@ -75,7 +78,7 @@ export default defineConfig([
'import-x/resolver': {'typescript': true}, 'import-x/resolver': {'typescript': true},
}, },
rules: { rules: {
'@eslint-community/eslint-comments/disable-enable-pair': [2], '@eslint-community/eslint-comments/disable-enable-pair': [0],
'@eslint-community/eslint-comments/no-aggregating-enable': [2], '@eslint-community/eslint-comments/no-aggregating-enable': [2],
'@eslint-community/eslint-comments/no-duplicate-disable': [2], '@eslint-community/eslint-comments/no-duplicate-disable': [2],
'@eslint-community/eslint-comments/no-restricted-disable': [0], '@eslint-community/eslint-comments/no-restricted-disable': [0],
@@ -155,11 +158,11 @@ export default defineConfig([
'@typescript-eslint/adjacent-overload-signatures': [0], '@typescript-eslint/adjacent-overload-signatures': [0],
'@typescript-eslint/array-type': [0], '@typescript-eslint/array-type': [0],
'@typescript-eslint/await-thenable': [2], '@typescript-eslint/await-thenable': [2],
'@typescript-eslint/ban-ts-comment': [2, {'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}], '@typescript-eslint/ban-ts-comment': [2, {'ts-expect-error': true, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}],
'@typescript-eslint/ban-tslint-comment': [0], '@typescript-eslint/ban-tslint-comment': [0],
'@typescript-eslint/class-literal-property-style': [0], '@typescript-eslint/class-literal-property-style': [0],
'@typescript-eslint/class-methods-use-this': [0], '@typescript-eslint/class-methods-use-this': [0],
'@typescript-eslint/consistent-generic-constructors': [0], '@typescript-eslint/consistent-generic-constructors': [2, 'constructor'],
'@typescript-eslint/consistent-indexed-object-style': [0], '@typescript-eslint/consistent-indexed-object-style': [0],
'@typescript-eslint/consistent-return': [0], '@typescript-eslint/consistent-return': [0],
'@typescript-eslint/consistent-type-assertions': [2, {assertionStyle: 'as', objectLiteralTypeAssertions: 'allow'}], '@typescript-eslint/consistent-type-assertions': [2, {assertionStyle: 'as', objectLiteralTypeAssertions: 'allow'}],
@@ -178,7 +181,7 @@ export default defineConfig([
'@typescript-eslint/naming-convention': [0], '@typescript-eslint/naming-convention': [0],
'@typescript-eslint/no-array-constructor': [2], '@typescript-eslint/no-array-constructor': [2],
'@typescript-eslint/no-array-delete': [2], '@typescript-eslint/no-array-delete': [2],
'@typescript-eslint/no-base-to-string': [0], '@typescript-eslint/no-base-to-string': [2],
'@typescript-eslint/no-confusing-non-null-assertion': [2], '@typescript-eslint/no-confusing-non-null-assertion': [2],
'@typescript-eslint/no-confusing-void-expression': [0], '@typescript-eslint/no-confusing-void-expression': [0],
'@typescript-eslint/no-deprecated': [2], '@typescript-eslint/no-deprecated': [2],
@@ -210,7 +213,7 @@ export default defineConfig([
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': [0], '@typescript-eslint/no-non-null-asserted-nullish-coalescing': [0],
'@typescript-eslint/no-non-null-asserted-optional-chain': [2], '@typescript-eslint/no-non-null-asserted-optional-chain': [2],
'@typescript-eslint/no-non-null-assertion': [0], '@typescript-eslint/no-non-null-assertion': [0],
'@typescript-eslint/no-redeclare': [0], '@typescript-eslint/no-redeclare': [2],
'@typescript-eslint/no-redundant-type-constituents': [2], '@typescript-eslint/no-redundant-type-constituents': [2],
'@typescript-eslint/no-require-imports': [2], '@typescript-eslint/no-require-imports': [2],
'@typescript-eslint/no-restricted-imports': [0], '@typescript-eslint/no-restricted-imports': [0],
@@ -221,7 +224,7 @@ export default defineConfig([
'@typescript-eslint/no-unnecessary-condition': [0], '@typescript-eslint/no-unnecessary-condition': [0],
'@typescript-eslint/no-unnecessary-qualifier': [0], '@typescript-eslint/no-unnecessary-qualifier': [0],
'@typescript-eslint/no-unnecessary-template-expression': [0], '@typescript-eslint/no-unnecessary-template-expression': [0],
'@typescript-eslint/no-unnecessary-type-arguments': [0], '@typescript-eslint/no-unnecessary-type-arguments': [2],
'@typescript-eslint/no-unnecessary-type-assertion': [2], '@typescript-eslint/no-unnecessary-type-assertion': [2],
'@typescript-eslint/no-unnecessary-type-constraint': [2], '@typescript-eslint/no-unnecessary-type-constraint': [2],
'@typescript-eslint/no-unnecessary-type-conversion': [2], '@typescript-eslint/no-unnecessary-type-conversion': [2],
@@ -234,10 +237,12 @@ export default defineConfig([
'@typescript-eslint/no-unsafe-member-access': [0], '@typescript-eslint/no-unsafe-member-access': [0],
'@typescript-eslint/no-unsafe-return': [0], '@typescript-eslint/no-unsafe-return': [0],
'@typescript-eslint/no-unsafe-unary-minus': [2], '@typescript-eslint/no-unsafe-unary-minus': [2],
'@typescript-eslint/no-unused-expressions': [0], '@typescript-eslint/no-unused-expressions': [2],
'@typescript-eslint/no-unused-private-class-members': [2],
'@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}], '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}],
'@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}], '@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}],
'@typescript-eslint/no-useless-constructor': [0], '@typescript-eslint/no-useless-constructor': [0],
'@typescript-eslint/no-useless-default-assignment': [2],
'@typescript-eslint/no-useless-empty-export': [0], '@typescript-eslint/no-useless-empty-export': [0],
'@typescript-eslint/no-wrapper-object-types': [2], '@typescript-eslint/no-wrapper-object-types': [2],
'@typescript-eslint/non-nullable-type-assertion-style': [0], '@typescript-eslint/non-nullable-type-assertion-style': [0],
@@ -251,10 +256,10 @@ export default defineConfig([
'@typescript-eslint/prefer-function-type': [2], '@typescript-eslint/prefer-function-type': [2],
'@typescript-eslint/prefer-includes': [2], '@typescript-eslint/prefer-includes': [2],
'@typescript-eslint/prefer-literal-enum-member': [0], '@typescript-eslint/prefer-literal-enum-member': [0],
'@typescript-eslint/prefer-namespace-keyword': [0], '@typescript-eslint/prefer-namespace-keyword': [2],
'@typescript-eslint/prefer-nullish-coalescing': [0], '@typescript-eslint/prefer-nullish-coalescing': [0],
'@typescript-eslint/prefer-optional-chain': [2, {requireNullish: true}], '@typescript-eslint/prefer-optional-chain': [2, {requireNullish: true}],
'@typescript-eslint/prefer-promise-reject-errors': [0], '@typescript-eslint/prefer-promise-reject-errors': [2],
'@typescript-eslint/prefer-readonly': [0], '@typescript-eslint/prefer-readonly': [0],
'@typescript-eslint/prefer-readonly-parameter-types': [0], '@typescript-eslint/prefer-readonly-parameter-types': [0],
'@typescript-eslint/prefer-reduce-type-parameter': [0], '@typescript-eslint/prefer-reduce-type-parameter': [0],
@@ -265,9 +270,10 @@ export default defineConfig([
'@typescript-eslint/require-array-sort-compare': [0], '@typescript-eslint/require-array-sort-compare': [0],
'@typescript-eslint/require-await': [0], '@typescript-eslint/require-await': [0],
'@typescript-eslint/restrict-plus-operands': [2], '@typescript-eslint/restrict-plus-operands': [2],
'@typescript-eslint/restrict-template-expressions': [0], '@typescript-eslint/restrict-template-expressions': [2],
'@typescript-eslint/return-await': [0], '@typescript-eslint/return-await': [0],
'@typescript-eslint/strict-boolean-expressions': [0], '@typescript-eslint/strict-boolean-expressions': [0],
'@typescript-eslint/strict-void-return': [0],
'@typescript-eslint/switch-exhaustiveness-check': [0], '@typescript-eslint/switch-exhaustiveness-check': [0],
'@typescript-eslint/triple-slash-reference': [2], '@typescript-eslint/triple-slash-reference': [2],
'@typescript-eslint/typedef': [0], '@typescript-eslint/typedef': [0],
@@ -291,6 +297,8 @@ export default defineConfig([
'consistent-this': [0], 'consistent-this': [0],
'constructor-super': [2], 'constructor-super': [2],
'curly': [0], 'curly': [0],
'de-morgan/no-negated-conjunction': [2],
'de-morgan/no-negated-disjunction': [2],
'default-case-last': [2], 'default-case-last': [2],
'default-case': [0], 'default-case': [0],
'default-param-last': [0], 'default-param-last': [0],
@@ -321,7 +329,7 @@ export default defineConfig([
'github/no-innerText': [2], 'github/no-innerText': [2],
'github/no-then': [2], 'github/no-then': [2],
'github/no-useless-passive': [2], 'github/no-useless-passive': [2],
'github/prefer-observers': [2], 'github/prefer-observers': [0],
'github/require-passive-events': [2], 'github/require-passive-events': [2],
'github/unescaped-html-literal': [2], 'github/unescaped-html-literal': [2],
'grouped-accessor-pairs': [2], 'grouped-accessor-pairs': [2],
@@ -338,7 +346,7 @@ export default defineConfig([
'import-x/first': [2], 'import-x/first': [2],
'import-x/group-exports': [0], 'import-x/group-exports': [0],
'import-x/max-dependencies': [0], 'import-x/max-dependencies': [0],
'import-x/named': [2], 'import-x/named': [0],
'import-x/namespace': [0], 'import-x/namespace': [0],
'import-x/newline-after-import': [0], 'import-x/newline-after-import': [0],
'import-x/no-absolute-path': [0], 'import-x/no-absolute-path': [0],
@@ -366,7 +374,7 @@ export default defineConfig([
'import-x/no-self-import': [2], 'import-x/no-self-import': [2],
'import-x/no-unassigned-import': [0], 'import-x/no-unassigned-import': [0],
'import-x/no-unresolved': [2, {commonjs: true, ignore: ['\\?.+$']}], 'import-x/no-unresolved': [2, {commonjs: true, ignore: ['\\?.+$']}],
// 'import-x/no-unused-modules': [2, {unusedExports: true}], // not compatible with eslint 9 'import-x/no-unused-modules': [0], // incompatible with eslint 9
'import-x/no-useless-path-segments': [2, {commonjs: true}], 'import-x/no-useless-path-segments': [2, {commonjs: true}],
'import-x/no-webpack-loader-syntax': [2], 'import-x/no-webpack-loader-syntax': [2],
'import-x/order': [0], 'import-x/order': [0],
@@ -433,7 +441,7 @@ export default defineConfig([
'no-import-assign': [2], 'no-import-assign': [2],
'no-inline-comments': [0], 'no-inline-comments': [0],
'no-inner-declarations': [2], 'no-inner-declarations': [2],
'no-invalid-regexp': [2], 'no-invalid-regexp': [0], // handled by regexp/no-invalid-regexp
'no-invalid-this': [0], 'no-invalid-this': [0],
'no-irregular-whitespace': [2], 'no-irregular-whitespace': [2],
'no-iterator': [2], 'no-iterator': [2],
@@ -547,7 +555,7 @@ export default defineConfig([
'no-new-func': [0], // handled by @typescript-eslint/no-implied-eval 'no-new-func': [0], // handled by @typescript-eslint/no-implied-eval
'no-new-native-nonconstructor': [2], 'no-new-native-nonconstructor': [2],
'no-new-object': [2], 'no-new-object': [2],
'no-new-symbol': [2], 'no-new-symbol': [0], // handled by no-new-native-nonconstructor
'no-new-wrappers': [2], 'no-new-wrappers': [2],
'no-new': [0], 'no-new': [0],
'no-nonoctal-decimal-escape': [2], 'no-nonoctal-decimal-escape': [2],
@@ -562,9 +570,14 @@ export default defineConfig([
'no-redeclare': [0], // must be disabled for typescript overloads 'no-redeclare': [0], // must be disabled for typescript overloads
'no-regex-spaces': [2], 'no-regex-spaces': [2],
'no-restricted-exports': [0], 'no-restricted-exports': [0],
'no-restricted-globals': [2, 'addEventListener', 'blur', 'close', 'closed', 'confirm', 'defaultStatus', 'defaultstatus', 'error', 'event', 'external', 'find', 'focus', 'frameElement', 'frames', 'history', 'innerHeight', 'innerWidth', 'isFinite', 'isNaN', 'length', 'locationbar', 'menubar', 'moveBy', 'moveTo', 'name', 'onblur', 'onerror', 'onfocus', 'onload', 'onresize', 'onunload', 'open', 'opener', 'opera', 'outerHeight', 'outerWidth', 'pageXOffset', 'pageYOffset', 'parent', 'print', 'removeEventListener', 'resizeBy', 'resizeTo', 'screen', 'screenLeft', 'screenTop', 'screenX', 'screenY', 'scroll', 'scrollbars', 'scrollBy', 'scrollTo', 'scrollX', 'scrollY', 'status', 'statusbar', 'stop', 'toolbar', 'top'], 'no-restricted-globals': [2, ...restrictedGlobals],
'no-restricted-imports': [0], 'no-restricted-properties': [2, ...restrictedProperties],
'no-restricted-syntax': [2, ...restrictedSyntax, {selector: 'CallExpression[callee.name="fetch"]', message: 'use modules/fetch.ts instead'}], 'no-restricted-imports': [2, {paths: [
{name: 'jquery', message: 'Use the global $ instead', allowTypeImports: true},
{name: 'htmx.org', message: 'Use the global htmx instead', allowTypeImports: true},
{name: 'idiomorph/htmx', message: 'Loaded in globals.ts', allowTypeImports: true},
]}],
'no-restricted-syntax': [2, 'WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression'],
'no-return-assign': [0], 'no-return-assign': [0],
'no-script-url': [2], 'no-script-url': [2],
'no-self-assign': [2, {props: true}], 'no-self-assign': [2, {props: true}],
@@ -577,10 +590,11 @@ export default defineConfig([
'no-template-curly-in-string': [2], 'no-template-curly-in-string': [2],
'no-ternary': [0], 'no-ternary': [0],
'no-this-before-super': [2], 'no-this-before-super': [2],
'no-throw-literal': [2], 'no-throw-literal': [0], // handled by @typescript-eslint/only-throw-error
'no-undef-init': [2], 'no-undef-init': [2],
'no-undef': [2], // it is still needed by eslint & IDE to prompt undefined names in real time 'no-undef': [2], // it is still needed by eslint & IDE to prompt undefined names in real time
'no-undefined': [0], 'no-undefined': [0],
'no-unassigned-vars': [2],
'no-underscore-dangle': [0], 'no-underscore-dangle': [0],
'no-unexpected-multiline': [2], 'no-unexpected-multiline': [2],
'no-unmodified-loop-condition': [2], 'no-unmodified-loop-condition': [2],
@@ -589,13 +603,13 @@ export default defineConfig([
'no-unreachable': [2], 'no-unreachable': [2],
'no-unsafe-finally': [2], 'no-unsafe-finally': [2],
'no-unsafe-negation': [2], 'no-unsafe-negation': [2],
'no-unused-expressions': [2], 'no-unused-expressions': [0], // handled by @typescript-eslint/no-unused-expressions
'no-unused-labels': [2], 'no-unused-labels': [2],
'no-unused-private-class-members': [2], 'no-unused-private-class-members': [0], // handled by @typescript-eslint/no-unused-private-class-members
'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars 'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars
'no-use-before-define': [0], // handled by @typescript-eslint/no-use-before-define 'no-use-before-define': [0], // handled by @typescript-eslint/no-use-before-define
'no-use-extend-native/no-use-extend-native': [2], 'no-useless-assignment': [2],
'no-useless-backreference': [2], 'no-useless-backreference': [0], // handled by regexp/no-useless-backreference
'no-useless-call': [2], 'no-useless-call': [2],
'no-useless-catch': [2], 'no-useless-catch': [2],
'no-useless-computed-key': [2], 'no-useless-computed-key': [2],
@@ -603,7 +617,7 @@ export default defineConfig([
'no-useless-constructor': [2], 'no-useless-constructor': [2],
'no-useless-escape': [2], 'no-useless-escape': [2],
'no-useless-rename': [2], 'no-useless-rename': [2],
'no-useless-return': [2], 'no-useless-return': [0], // handled by sonarjs/no-redundant-jump
'no-var': [2], 'no-var': [2],
'no-void': [2], 'no-void': [2],
'no-warning-comments': [0], 'no-warning-comments': [0],
@@ -612,7 +626,7 @@ export default defineConfig([
'one-var-declaration-per-line': [0], 'one-var-declaration-per-line': [0],
'one-var': [0], 'one-var': [0],
'operator-assignment': [2, 'always'], 'operator-assignment': [2, 'always'],
'operator-linebreak': [2, 'after'], 'operator-linebreak': [0], // handled by @stylistic/operator-linebreak
'prefer-arrow-callback': [2, {allowNamedFunctions: true, allowUnboundThis: true}], 'prefer-arrow-callback': [2, {allowNamedFunctions: true, allowUnboundThis: true}],
'prefer-const': [2, {destructuring: 'all', ignoreReadBeforeAssign: true}], 'prefer-const': [2, {destructuring: 'all', ignoreReadBeforeAssign: true}],
'prefer-destructuring': [0], 'prefer-destructuring': [0],
@@ -621,19 +635,20 @@ export default defineConfig([
'prefer-numeric-literals': [2], 'prefer-numeric-literals': [2],
'prefer-object-has-own': [2], 'prefer-object-has-own': [2],
'prefer-object-spread': [2], 'prefer-object-spread': [2],
'prefer-promise-reject-errors': [2, {allowEmptyReject: false}], 'prefer-promise-reject-errors': [0], // handled by @typescript-eslint/prefer-promise-reject-errors
'prefer-regex-literals': [2], 'prefer-regex-literals': [2],
'prefer-rest-params': [2], 'prefer-rest-params': [2],
'prefer-spread': [2], 'prefer-spread': [2],
'prefer-template': [2], 'prefer-template': [2],
'radix': [2, 'as-needed'], 'preserve-caught-error': [0],
'radix': [0],
'regexp/confusing-quantifier': [2], 'regexp/confusing-quantifier': [2],
'regexp/control-character-escape': [2], 'regexp/control-character-escape': [2],
'regexp/hexadecimal-escape': [0], 'regexp/hexadecimal-escape': [0],
'regexp/letter-case': [0], 'regexp/letter-case': [0],
'regexp/match-any': [2], 'regexp/match-any': [2],
'regexp/negation': [2], 'regexp/negation': [2],
'regexp/no-contradiction-with-assertion': [0], 'regexp/no-contradiction-with-assertion': [2],
'regexp/no-control-character': [0], 'regexp/no-control-character': [0],
'regexp/no-dupe-characters-character-class': [2], 'regexp/no-dupe-characters-character-class': [2],
'regexp/no-dupe-disjunctions': [2], 'regexp/no-dupe-disjunctions': [2],
@@ -649,8 +664,8 @@ export default defineConfig([
'regexp/no-invisible-character': [2], 'regexp/no-invisible-character': [2],
'regexp/no-lazy-ends': [2], 'regexp/no-lazy-ends': [2],
'regexp/no-legacy-features': [2], 'regexp/no-legacy-features': [2],
'regexp/no-misleading-capturing-group': [0], 'regexp/no-misleading-capturing-group': [2],
'regexp/no-misleading-unicode-character': [0], 'regexp/no-misleading-unicode-character': [2],
'regexp/no-missing-g-flag': [2], 'regexp/no-missing-g-flag': [2],
'regexp/no-non-standard-flag': [2], 'regexp/no-non-standard-flag': [2],
'regexp/no-obscure-range': [2], 'regexp/no-obscure-range': [2],
@@ -692,7 +707,7 @@ export default defineConfig([
'regexp/prefer-question-quantifier': [2], 'regexp/prefer-question-quantifier': [2],
'regexp/prefer-range': [2], 'regexp/prefer-range': [2],
'regexp/prefer-regexp-exec': [2], 'regexp/prefer-regexp-exec': [2],
'regexp/prefer-regexp-test': [2], 'regexp/prefer-regexp-test': [0], // handled by unicorn/prefer-regexp-test
'regexp/prefer-result-array-groups': [0], 'regexp/prefer-result-array-groups': [0],
'regexp/prefer-set-operation': [2], 'regexp/prefer-set-operation': [2],
'regexp/prefer-star-quantifier': [2], 'regexp/prefer-star-quantifier': [2],
@@ -722,7 +737,7 @@ export default defineConfig([
'sonarjs/no-empty-collection': [2], 'sonarjs/no-empty-collection': [2],
'sonarjs/no-extra-arguments': [2], 'sonarjs/no-extra-arguments': [2],
'sonarjs/no-gratuitous-expressions': [2], 'sonarjs/no-gratuitous-expressions': [2],
'sonarjs/no-identical-conditions': [2], 'sonarjs/no-identical-conditions': [0], // handled by no-dupe-else-if
'sonarjs/no-identical-expressions': [2], 'sonarjs/no-identical-expressions': [2],
'sonarjs/no-identical-functions': [2, 5], 'sonarjs/no-identical-functions': [2, 5],
'sonarjs/no-ignored-return': [2], 'sonarjs/no-ignored-return': [2],
@@ -735,7 +750,7 @@ export default defineConfig([
'sonarjs/no-small-switch': [0], 'sonarjs/no-small-switch': [0],
'sonarjs/no-unused-collection': [2], 'sonarjs/no-unused-collection': [2],
'sonarjs/no-use-of-empty-return-value': [2], 'sonarjs/no-use-of-empty-return-value': [2],
'sonarjs/no-useless-catch': [2], 'sonarjs/no-useless-catch': [0], // handled by no-useless-catch
'sonarjs/non-existent-operator': [2], 'sonarjs/non-existent-operator': [2],
'sonarjs/prefer-immediate-return': [0], 'sonarjs/prefer-immediate-return': [0],
'sonarjs/prefer-object-literal': [0], 'sonarjs/prefer-object-literal': [0],
@@ -751,6 +766,7 @@ export default defineConfig([
'unicorn/catch-error-name': [0], 'unicorn/catch-error-name': [0],
'unicorn/consistent-destructuring': [2], 'unicorn/consistent-destructuring': [2],
'unicorn/consistent-empty-array-spread': [2], 'unicorn/consistent-empty-array-spread': [2],
'unicorn/consistent-template-literal-escape': [2],
'unicorn/consistent-existence-index-check': [0], 'unicorn/consistent-existence-index-check': [0],
'unicorn/consistent-function-scoping': [0], 'unicorn/consistent-function-scoping': [0],
'unicorn/custom-error-definition': [0], 'unicorn/custom-error-definition': [0],
@@ -762,6 +778,7 @@ export default defineConfig([
'unicorn/filename-case': [0], 'unicorn/filename-case': [0],
'unicorn/import-index': [0], 'unicorn/import-index': [0],
'unicorn/import-style': [0], 'unicorn/import-style': [0],
'unicorn/isolated-functions': [2, {functions: []}],
'unicorn/new-for-builtins': [2], 'unicorn/new-for-builtins': [2],
'unicorn/no-abusive-eslint-disable': [0], 'unicorn/no-abusive-eslint-disable': [0],
'unicorn/no-anonymous-default-export': [0], 'unicorn/no-anonymous-default-export': [0],
@@ -777,6 +794,7 @@ export default defineConfig([
'unicorn/no-empty-file': [2], 'unicorn/no-empty-file': [2],
'unicorn/no-for-loop': [0], 'unicorn/no-for-loop': [0],
'unicorn/no-hex-escape': [0], 'unicorn/no-hex-escape': [0],
'unicorn/no-immediate-mutation': [0],
'unicorn/no-instanceof-array': [0], 'unicorn/no-instanceof-array': [0],
'unicorn/no-invalid-fetch-options': [2], 'unicorn/no-invalid-fetch-options': [2],
'unicorn/no-invalid-remove-event-listener': [2], 'unicorn/no-invalid-remove-event-listener': [2],
@@ -800,9 +818,11 @@ export default defineConfig([
'unicorn/no-unnecessary-await': [2], 'unicorn/no-unnecessary-await': [2],
'unicorn/no-unnecessary-polyfills': [2], 'unicorn/no-unnecessary-polyfills': [2],
'unicorn/no-unreadable-array-destructuring': [0], 'unicorn/no-unreadable-array-destructuring': [0],
'unicorn/no-unreadable-iife': [2], 'unicorn/no-unreadable-iife': [0],
'unicorn/no-unused-properties': [2], 'unicorn/no-unused-properties': [2],
'unicorn/no-useless-collection-argument': [2],
'unicorn/no-useless-fallback-in-spread': [2], 'unicorn/no-useless-fallback-in-spread': [2],
'unicorn/no-useless-iterator-to-array': [2],
'unicorn/no-useless-length-check': [2], 'unicorn/no-useless-length-check': [2],
'unicorn/no-useless-promise-resolve-reject': [2], 'unicorn/no-useless-promise-resolve-reject': [2],
'unicorn/no-useless-spread': [2], 'unicorn/no-useless-spread': [2],
@@ -812,9 +832,9 @@ export default defineConfig([
'unicorn/number-literal-case': [0], 'unicorn/number-literal-case': [0],
'unicorn/numeric-separators-style': [0], 'unicorn/numeric-separators-style': [0],
'unicorn/prefer-add-event-listener': [2], 'unicorn/prefer-add-event-listener': [2],
'unicorn/prefer-array-find': [2], 'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
'unicorn/prefer-array-flat-map': [2],
'unicorn/prefer-array-flat': [2], 'unicorn/prefer-array-flat': [2],
'unicorn/prefer-array-flat-map': [2],
'unicorn/prefer-array-index-of': [2], 'unicorn/prefer-array-index-of': [2],
'unicorn/prefer-array-some': [2], 'unicorn/prefer-array-some': [2],
'unicorn/prefer-at': [0], 'unicorn/prefer-at': [0],
@@ -829,7 +849,7 @@ export default defineConfig([
'unicorn/prefer-event-target': [2], 'unicorn/prefer-event-target': [2],
'unicorn/prefer-export-from': [0], 'unicorn/prefer-export-from': [0],
'unicorn/prefer-global-this': [0], 'unicorn/prefer-global-this': [0],
'unicorn/prefer-includes': [2], 'unicorn/prefer-includes': [0], // handled by @typescript-eslint/prefer-includes
'unicorn/prefer-json-parse-buffer': [0], 'unicorn/prefer-json-parse-buffer': [0],
'unicorn/prefer-keyboard-event-key': [2], 'unicorn/prefer-keyboard-event-key': [2],
'unicorn/prefer-logical-operator-over-ternary': [2], 'unicorn/prefer-logical-operator-over-ternary': [2],
@@ -849,13 +869,15 @@ export default defineConfig([
'unicorn/prefer-query-selector': [2], 'unicorn/prefer-query-selector': [2],
'unicorn/prefer-reflect-apply': [0], 'unicorn/prefer-reflect-apply': [0],
'unicorn/prefer-regexp-test': [2], 'unicorn/prefer-regexp-test': [2],
'unicorn/prefer-response-static-json': [2],
'unicorn/prefer-set-has': [0], 'unicorn/prefer-set-has': [0],
'unicorn/prefer-set-size': [2], 'unicorn/prefer-set-size': [2],
'unicorn/prefer-simple-condition-first': [0],
'unicorn/prefer-spread': [0], 'unicorn/prefer-spread': [0],
'unicorn/prefer-string-raw': [0], 'unicorn/prefer-string-raw': [0],
'unicorn/prefer-string-replace-all': [0], 'unicorn/prefer-string-replace-all': [0],
'unicorn/prefer-string-slice': [0], 'unicorn/prefer-string-slice': [0],
'unicorn/prefer-string-starts-ends-with': [2], 'unicorn/prefer-string-starts-ends-with': [0], // handled by @typescript-eslint/prefer-string-starts-ends-with
'unicorn/prefer-string-trim-start-end': [2], 'unicorn/prefer-string-trim-start-end': [2],
'unicorn/prefer-structured-clone': [2], 'unicorn/prefer-structured-clone': [2],
'unicorn/prefer-switch': [0], 'unicorn/prefer-switch': [0],
@@ -869,6 +891,7 @@ export default defineConfig([
'unicorn/require-post-message-target-origin': [0], 'unicorn/require-post-message-target-origin': [0],
'unicorn/string-content': [0], 'unicorn/string-content': [0],
'unicorn/switch-case-braces': [0], 'unicorn/switch-case-braces': [0],
'unicorn/switch-case-break-position': [2],
'unicorn/template-indent': [2], 'unicorn/template-indent': [2],
'unicorn/text-encoding-identifier-case': [0], 'unicorn/text-encoding-identifier-case': [0],
'unicorn/throw-new-error': [2], 'unicorn/throw-new-error': [2],
@@ -900,12 +923,13 @@ export default defineConfig([
'yoda': [2, 'never'], 'yoda': [2, 'never'],
}, },
}, },
// @ts-expect-error
{ {
...playwright.configs['flat/recommended'], ...playwright.configs['flat/recommended'],
files: ['tests/e2e/**'], files: ['tests/e2e/**/*.test.ts'],
languageOptions: {globals: {...globals.nodeBuiltin, ...globals.browser}},
rules: { rules: {
...playwright.configs['flat/recommended'].rules, ...playwright.configs['flat/recommended'].rules,
'playwright/expect-expect': [0],
}, },
}, },
{ {
@@ -916,27 +940,19 @@ export default defineConfig([
}, },
}, },
extends: [ extends: [
// @ts-expect-error
vue.configs['flat/recommended'], vue.configs['flat/recommended'],
// @ts-expect-error vueScopedCss.configs.recommended as any,
vueScopedCss.configs['flat/recommended'],
], ],
rules: { rules: {
'vue/attributes-order': [0], 'vue/attributes-order': [0],
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}], 'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
'vue/max-attributes-per-line': [0], 'vue/max-attributes-per-line': [0],
'vue/singleline-html-element-content-newline': [0], 'vue/singleline-html-element-content-newline': [0],
}, 'vue/require-typed-ref': [2],
},
{
files: ['web_src/js/modules/fetch.ts', 'web_src/js/standalone/**/*'],
rules: {
'no-restricted-syntax': [2, ...restrictedSyntax],
}, },
}, },
{ {
files: ['**/*.test.ts', 'web_src/js/test/setup.ts'], files: ['**/*.test.ts', 'web_src/js/test/setup.ts'],
// @ts-expect-error - https://github.com/vitest-dev/eslint-plugin-vitest/issues/737
plugins: {vitest}, plugins: {vitest},
languageOptions: {globals: globals.vitest}, languageOptions: {globals: globals.vitest},
rules: { rules: {
@@ -960,6 +976,7 @@ export default defineConfig([
'vitest/no-interpolation-in-snapshots': [0], 'vitest/no-interpolation-in-snapshots': [0],
'vitest/no-large-snapshots': [0], 'vitest/no-large-snapshots': [0],
'vitest/no-mocks-import': [0], 'vitest/no-mocks-import': [0],
'vitest/no-importing-vitest-globals': [2],
'vitest/no-restricted-matchers': [0], 'vitest/no-restricted-matchers': [0],
'vitest/no-restricted-vi-methods': [0], 'vitest/no-restricted-vi-methods': [0],
'vitest/no-standalone-expect': [0], 'vitest/no-standalone-expect': [0],
@@ -988,42 +1005,24 @@ export default defineConfig([
'vitest/require-to-throw-message': [0], 'vitest/require-to-throw-message': [0],
'vitest/require-top-level-describe': [0], 'vitest/require-top-level-describe': [0],
'vitest/valid-describe-callback': [2], 'vitest/valid-describe-callback': [2],
'vitest/valid-expect': [2], 'vitest/valid-expect': [2, {maxArgs: 2}],
'vitest/valid-expect-in-promise': [2],
'vitest/valid-title': [2], 'vitest/valid-title': [2],
}, },
}, },
{
files: ['web_src/js/types.ts'],
rules: {
'import-x/no-unused-modules': [0],
},
},
{ {
files: ['**/*.d.ts'], files: ['**/*.d.ts'],
rules: { rules: {
'import-x/no-unused-modules': [0],
'@typescript-eslint/consistent-type-definitions': [0], '@typescript-eslint/consistent-type-definitions': [0],
'@typescript-eslint/consistent-type-imports': [0], '@typescript-eslint/consistent-type-imports': [0],
}, },
}, },
{ {
files: ['*.config.*'], files: ['*', 'tools/**/*', 'tests/**/*'],
rules: { languageOptions: {globals: globals.nodeBuiltin},
'import-x/no-unused-modules': [0],
},
},
{
files: ['web_src/**/*', 'docs/**/*'],
languageOptions: {globals: globals.browser},
}, },
{ {
files: ['web_src/**/*'], files: ['web_src/**/*'],
languageOptions: { languageOptions: {globals: {...globals.browser, ...globals.jquery, htmx: false}},
globals: {
...globals.browser,
__webpack_public_path__: true,
process: false, // https://github.com/webpack/webpack/issues/15833
},
},
}, },
]); ]);
@@ -0,0 +1,30 @@
import {defineConfig, globalIgnores} from 'eslint/config';
import json from '@eslint/json';
export default defineConfig([
globalIgnores([
'**/.venv',
'**/node_modules',
'**/public',
]),
{
files: ['**/*.json'],
plugins: {json},
language: 'json/json',
extends: ['json/recommended'],
},
{
files: [
'tsconfig.json',
'.devcontainer/*.json',
'.vscode/*.json',
'contrib/ide/vscode/*.json',
],
plugins: {json},
language: 'json/jsonc',
languageOptions: {
allowTrailingCommas: true,
},
extends: ['json/recommended'],
},
]);
+3 -37
View File
@@ -1,30 +1,12 @@
{ {
"nodes": { "nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1755186698, "lastModified": 1775036866,
"narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -36,24 +18,8 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",
+39 -18
View File
@@ -1,38 +1,57 @@
{ {
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = outputs =
{ nixpkgs, flake-utils, ... }: { nixpkgs, ... }:
flake-utils.lib.eachDefaultSystem ( let
supportedSystems = [
"aarch64-darwin"
"aarch64-linux"
"x86_64-darwin"
"x86_64-linux"
];
forEachSupportedSystem =
f:
nixpkgs.lib.genAttrs supportedSystems (
system: system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = import nixpkgs {
inherit system;
};
in
f { inherit pkgs; }
);
in in
{ {
devShells.default = devShells = forEachSupportedSystem (
with pkgs; { pkgs, ... }:
{
default =
let let
inherit (pkgs) lib;
# only bump toolchain versions here # only bump toolchain versions here
go = go_1_25; go = pkgs.go_1_26;
nodejs = nodejs_24; nodejs = pkgs.nodejs_24;
python3 = python312; python3 = pkgs.python314;
pnpm = pnpm_10; pnpm = pkgs.pnpm_10;
# Platform-specific dependencies # Platform-specific dependencies
linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [ linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [
glibc.static pkgs.glibc.static
]; ];
linuxOnlyEnv = lib.optionalAttrs pkgs.stdenv.isLinux { linuxOnlyEnv = lib.optionalAttrs pkgs.stdenv.isLinux {
CFLAGS = "-I${glibc.static.dev}/include"; CFLAGS = "-I${pkgs.glibc.static.dev}/include";
LDFLAGS = "-L ${glibc.static}/lib"; LDFLAGS = "-L ${pkgs.glibc.static}/lib";
}; };
in in
pkgs.mkShell ( pkgs.mkShell {
{ packages =
buildInputs = [ with pkgs;
[
# generic # generic
git git
git-lfs git-lfs
@@ -60,14 +79,16 @@
] ]
++ linuxOnlyInputs; ++ linuxOnlyInputs;
env = {
GO = "${go}/bin/go"; GO = "${go}/bin/go";
GOROOT = "${go}/share/go"; GOROOT = "${go}/share/go";
TAGS = "sqlite sqlite_unlock_notify"; TAGS = "sqlite sqlite_unlock_notify";
STATIC = "true"; STATIC = "true";
} }
// linuxOnlyEnv // linuxOnlyEnv;
); };
} }
); );
};
} }
+140 -142
View File
@@ -1,8 +1,6 @@
module code.gitea.io/gitea module code.gitea.io/gitea
go 1.25.0 go 1.26.3
toolchain go1.25.5
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related: // But some CAs use negative serial number, just relax the check. related:
@@ -11,208 +9,201 @@ godebug x509negativeserial=1
require ( require (
code.gitea.io/actions-proto-go v0.4.1 code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/gitea-vet v0.2.3 code.gitea.io/sdk/gitea v0.24.1
code.gitea.io/sdk/gitea v0.22.0
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.18.1 connectrpc.com/connect v1.19.1
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a
gitea.com/go-chi/cache v0.2.1 gitea.com/go-chi/cache v0.2.1
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/httpsig v1.2.3 github.com/42wim/httpsig v1.2.4
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920 github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Azure/go-ntlmssp v0.1.0
github.com/ProtonMail/go-crypto v1.3.0 github.com/ProtonMail/go-crypto v1.4.1
github.com/PuerkitoBio/goquery v1.10.3 github.com/PuerkitoBio/goquery v1.12.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
github.com/alecthomas/chroma/v2 v2.20.0 github.com/alecthomas/chroma/v2 v2.23.1
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 github.com/aws/aws-sdk-go-v2/credentials v1.19.13
github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2 github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.3 github.com/blevesearch/bleve/v2 v2.5.7
github.com/bohde/codel v0.2.0 github.com/bohde/codel v0.2.0
github.com/buildkite/terminal-to-html/v3 v3.16.8 github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.24.0 github.com/caddyserver/certmagic v0.25.2
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
github.com/chi-middleware/proxy v1.1.1 github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0 github.com/dlclark/regexp2 v1.11.5
github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.1 github.com/emirpasic/gods v1.18.1
github.com/ethantkoenig/rupture v1.0.1 github.com/ethantkoenig/rupture v1.0.1
github.com/felixge/fgprof v0.9.5 github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/gliderlabs/ssh v0.3.8 github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742 github.com/go-chi/chi/v5 v5.2.5
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron/v2 v2.19.1
github.com/go-enry/go-enry/v2 v2.9.2 github.com/go-enry/go-enry/v2 v2.9.5
github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-billy/v5 v5.9.0
github.com/go-git/go-git/v5 v5.16.3 github.com/go-git/go-git/v5 v5.19.0
github.com/go-ldap/ldap/v3 v3.4.11 github.com/go-ldap/ldap/v3 v3.4.13
github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-redsync/redsync/v4 v4.16.0
github.com/go-sql-driver/mysql v1.9.3 github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.13.4 github.com/go-webauthn/webauthn v0.16.1
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-github/v74 v74.0.0 github.com/google/go-github/v84 v84.0.0
github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0 github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/go-version v1.9.0
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.5.0 github.com/huandu/xstrings v1.5.0
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
github.com/jhillyerd/enmime v1.3.0 github.com/jhillyerd/enmime/v2 v2.3.0
github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.0 github.com/klauspost/compress v1.18.5
github.com/klauspost/cpuid/v2 v2.3.0 github.com/klauspost/cpuid/v2 v2.3.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.12.1
github.com/markbates/goth v1.82.0 github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.32 github.com/mattn/go-sqlite3 v1.14.38
github.com/meilisearch/meilisearch-go v0.33.2 github.com/meilisearch/meilisearch-go v0.36.1
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726 github.com/mholt/archives v0.1.5
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.9.3 github.com/microsoft/go-mssqldb v1.9.6
github.com/minio/minio-go/v7 v7.0.95 github.com/minio/minio-go/v7 v7.0.99
github.com/msteinert/pam v1.2.0 github.com/msteinert/pam/v2 v2.1.0
github.com/nektos/act v0.2.63 github.com/nektos/act v0.2.63
github.com/niklasfasching/go-org v1.9.1 github.com/niklasfasching/go-org v1.9.1
github.com/olivere/elastic/v7 v7.0.32 github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.5.0 github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_golang v1.23.2
github.com/quasoft/websspi v1.1.2 github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.12.1 github.com/redis/go-redis/v9 v9.18.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/sassoftware/go-rpmutils v0.4.0 github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.4.0 github.com/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0 github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.15 github.com/ulikunitz/xz v0.5.15
github.com/urfave/cli-docs/v3 v3.0.0-alpha6 github.com/urfave/cli-docs/v3 v3.1.0
github.com/urfave/cli/v3 v3.4.1 github.com/urfave/cli/v3 v3.4.1
github.com/wneessen/go-mail v0.7.2 github.com/wneessen/go-mail v0.7.2
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.13 github.com/yuin/goldmark v1.8.2
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 gitlab.com/gitlab-org/api/client-go v1.46.0
gitlab.com/gitlab-org/api/client-go v0.142.4 go.yaml.in/yaml/v4 v4.0.0-rc.3
golang.org/x/crypto v0.45.0 golang.org/x/crypto v0.50.0
golang.org/x/image v0.30.0 golang.org/x/image v0.38.0
golang.org/x/net v0.47.0 golang.org/x/net v0.53.0
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.18.0 golang.org/x/sync v0.20.0
golang.org/x/sys v0.38.0 golang.org/x/sys v0.44.0
golang.org/x/text v0.31.0 golang.org/x/text v0.36.0
google.golang.org/grpc v1.75.0 google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.8 google.golang.org/protobuf v1.36.11
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.6.0 mvdan.cc/xurls/v2 v2.6.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab
xorm.io/builder v0.3.13 xorm.io/builder v0.3.13
xorm.io/xorm v1.3.10 xorm.io/xorm v1.3.11
) )
require ( require (
cloud.google.com/go/compute/metadata v0.8.0 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect
code.gitea.io/gitea-vet v0.2.3 // indirect
dario.cat/mergo v1.0.2 // indirect dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.2.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/DataDog/zstd v1.5.7 // indirect github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect github.com/RoaringBitmap/roaring/v2 v2.16.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect github.com/STARRY-S/zip v0.2.3 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/brotli v1.2.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/smithy-go v1.23.0 // indirect github.com/aws/smithy-go v1.24.2 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.0 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/blevesearch/bleve_index_api v1.2.9 // indirect github.com/blevesearch/bleve_index_api v1.3.7 // indirect
github.com/blevesearch/geo v0.2.4 // indirect github.com/blevesearch/geo v0.2.5 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect github.com/blevesearch/go-faiss v1.0.30 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect github.com/blevesearch/mmap-go v1.2.0 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.11 // indirect github.com/blevesearch/scorch_segment_api/v2 v2.4.5 // indirect
github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect github.com/blevesearch/vellum v1.2.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect github.com/blevesearch/zapx/v11 v11.4.3 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect github.com/blevesearch/zapx/v12 v12.4.3 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect github.com/blevesearch/zapx/v13 v13.4.3 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect github.com/blevesearch/zapx/v14 v14.4.3 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect github.com/blevesearch/zapx/v15 v15.4.3 // indirect
github.com/blevesearch/zapx/v16 v16.2.4 // indirect github.com/blevesearch/zapx/v16 v16.3.2 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect github.com/boombuler/barcode v1.1.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect github.com/couchbase/gomemcached v0.3.4 // indirect
github.com/couchbase/goutils v0.1.2 // indirect github.com/couchbase/goutils v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.19.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-webauthn/x v0.1.24 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/go-webauthn/x v0.2.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/goccy/go-json v0.10.6 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.2.0 // indirect
github.com/google/go-tpm v0.9.5 // indirect github.com/google/go-tpm v0.9.8 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
@@ -220,53 +211,56 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inbucket/html2text v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v1.1.1 // indirect github.com/libdns/libdns v1.1.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.2 // indirect
github.com/markbates/going v1.0.3 // indirect github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/miekg/dns v1.1.68 // indirect github.com/miekg/dns v1.1.72 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.1 // indirect github.com/minio/minlz v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.0 // indirect github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.0.9 // indirect github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/philhofer/fwd v1.2.0 // indirect github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.17.0 // indirect github.com/prometheus/procfs v0.20.1 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect github.com/rhysd/actionlint v1.7.12 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.6.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/tinylib/msgp v1.4.0 // indirect github.com/tinylib/msgp v1.6.3 // indirect
github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
@@ -277,16 +271,16 @@ require (
go.etcd.io/bbolt v1.4.3 // indirect go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.1 // indirect
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.29.0 // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/mod v0.35.0 // indirect
golang.org/x/tools v0.38.0 // indirect golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )
ignore ( ignore (
@@ -294,18 +288,22 @@ ignore (
./node_modules ./node_modules
) )
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 // When doing "go get -u ./...", Golang will try to update all dependencies
// But not all latest versions of dependencies are compatible with other packages or our codebase, so we need to pin some dependencies to specific versions
// Need to regularly maintain this list to try to update them to latest versions, especially the TODO ones
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 // jaytaylor/html2text is unmaintained
replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763 replace github.com/nektos/act => gitea.com/gitea/act v0.261.10 // gitea maintains its own package
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 replace github.com/urfave/cli/v3 => github.com/urfave/cli/v3 v3.4.1 // v3.6.2 breaks -c flag parsing in help commands
exclude github.com/gofrs/uuid v3.2.0+incompatible replace go.yaml.in/yaml/v4 => go.yaml.in/yaml/v4 v4.0.0-rc.3 // rc.4 changes block scalar serialization, wait for stable release
exclude github.com/gofrs/uuid v4.0.0+incompatible replace github.com/Azure/azure-sdk-for-go/sdk/azcore => github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // v1.21.0+ uses API version unsupported by Azurite in CI
exclude github.com/goccy/go-json v0.4.11 replace github.com/Azure/azure-sdk-for-go/sdk/storage/azblob => github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 // v1.6.4+ uses API version unsupported by Azurite in CI
exclude github.com/satori/go.uuid v1.2.0 replace github.com/microsoft/go-mssqldb => github.com/microsoft/go-mssqldb v1.9.7 // downgraded with Azure SDK
tool code.gitea.io/gitea-vet
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -28,7 +28,6 @@ import (
var ( var (
Version = "development" // program version for this build Version = "development" // program version for this build
Tags = "" // the Golang build tags Tags = "" // the Golang build tags
MakeVersion = "" // "make" program version if built with make
) )
func init() { func init() {
@@ -44,14 +43,12 @@ func main() {
} }
app := cmd.NewMainApp(cmd.AppVersion{Version: Version, Extra: formatBuiltWith()}) app := cmd.NewMainApp(cmd.AppVersion{Version: Version, Extra: formatBuiltWith()})
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp _ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
// flush the queued logs before exiting, it is a MUST, otherwise there will be log loss
log.GetManager().Close() log.GetManager().Close()
} }
func formatBuiltWith() string { func formatBuiltWith() string {
version := runtime.Version() version := runtime.Version()
if len(MakeVersion) > 0 {
version = MakeVersion + ", " + runtime.Version()
}
if len(Tags) == 0 { if len(Tags) == 0 {
return " built with " + version return " built with " + version
} }
@@ -53,6 +53,11 @@ func init() {
db.RegisterModel(new(ActionArtifact)) db.RegisterModel(new(ActionArtifact))
} }
const (
ContentEncodingV3Gzip = "gzip"
ContentTypeZip = "application/zip"
)
// ActionArtifact is a file that is stored in the artifact storage. // ActionArtifact is a file that is stored in the artifact storage.
type ActionArtifact struct { type ActionArtifact struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@@ -64,7 +69,17 @@ type ActionArtifact struct {
StoragePath string // The path to the artifact in the storage StoragePath string // The path to the artifact in the storage
FileSize int64 // The size of the artifact in bytes FileSize int64 // The size of the artifact in bytes
FileCompressedSize int64 // The size of the artifact in bytes after gzip compression FileCompressedSize int64 // The size of the artifact in bytes after gzip compression
ContentEncoding string // The content encoding of the artifact
// The content encoding or content type of the artifact
// * empty or null: legacy (v3) uncompressed content
// * magic string "gzip" (ContentEncodingV3Gzip): v3 gzip compressed content
// * requires gzip decoding before storing in a zip for download
// * requires gzip content-encoding header when downloaded single files within a workflow
// * mime type for "Content-Type":
// * "application/zip" (ContentTypeZip), seems to be an abuse, fortunately there is no conflict, and it won't cause problems?
// * "application/pdf", "text/html", etc.: real content type of the artifact
ContentEncodingOrType string `xorm:"content_encoding"`
ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it
ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it
Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
@@ -156,7 +171,8 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond {
} }
if opts.FinalizedArtifactsV4 { if opts.FinalizedArtifactsV4 {
cond = cond.And(builder.Eq{"status": ArtifactStatusUploadConfirmed}.Or(builder.Eq{"status": ArtifactStatusExpired})) cond = cond.And(builder.Eq{"status": ArtifactStatusUploadConfirmed}.Or(builder.Eq{"status": ArtifactStatusExpired}))
cond = cond.And(builder.Eq{"content_encoding": "application/zip"}) // see the comment of ActionArtifact.ContentEncodingOrType: "*/*" means the field is a content type
cond = cond.And(builder.Like{"content_encoding", "%/%"})
} }
return cond return cond
@@ -170,10 +186,10 @@ type ActionArtifactMeta struct {
} }
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run // ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) { func ListUploadedArtifactsMeta(ctx context.Context, repoID, runID int64) ([]*ActionArtifactMeta, error) {
arts := make([]*ActionArtifactMeta, 0, 10) arts := make([]*ActionArtifactMeta, 0, 10)
return arts, db.GetEngine(ctx).Table("action_artifact"). return arts, db.GetEngine(ctx).Table("action_artifact").
Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired). Where("repo_id=? AND run_id=? AND (status=? OR status=?)", repoID, runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
GroupBy("artifact_name"). GroupBy("artifact_name").
Select("artifact_name, sum(file_size) as file_size, max(status) as status"). Select("artifact_name, sum(file_size) as file_size, max(status) as status").
Find(&arts) Find(&arts)
@@ -0,0 +1,74 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"context"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
"xorm.io/xorm/convert"
)
// OwnerActionsConfig defines the Actions configuration for a user or organization
type OwnerActionsConfig struct {
// TokenPermissionMode defines the default permission mode (permissive, restricted)
TokenPermissionMode repo_model.ActionsTokenPermissionMode `json:"token_permission_mode,omitempty"`
// MaxTokenPermissions defines the absolute maximum permissions any token can have in this context.
MaxTokenPermissions *repo_model.ActionsTokenPermissions `json:"max_token_permissions,omitempty"`
// AllowedCrossRepoIDs is a list of specific repo IDs that can be accessed cross-repo
AllowedCrossRepoIDs []int64 `json:"allowed_cross_repo_ids,omitempty"`
}
var _ convert.ConversionFrom = (*OwnerActionsConfig)(nil)
func (cfg *OwnerActionsConfig) FromDB(bytes []byte) error {
_ = json.Unmarshal(bytes, cfg)
cfg.TokenPermissionMode, _ = util.EnumValue(cfg.TokenPermissionMode)
return nil
}
// GetOwnerActionsConfig loads the OwnerActionsConfig for a user or organization from user settings
// It returns a default config if no setting is found
func GetOwnerActionsConfig(ctx context.Context, userID int64) (ret OwnerActionsConfig, err error) {
return user_model.GetUserSettingJSON(ctx, userID, user_model.SettingsKeyActionsConfig, ret)
}
// SetOwnerActionsConfig saves the OwnerActionsConfig for a user or organization to user settings
func SetOwnerActionsConfig(ctx context.Context, userID int64, cfg OwnerActionsConfig) error {
return user_model.SetUserSettingJSON(ctx, userID, user_model.SettingsKeyActionsConfig, cfg)
}
// GetDefaultTokenPermissions returns the default token permissions by its TokenPermissionMode.
func (cfg *OwnerActionsConfig) GetDefaultTokenPermissions() repo_model.ActionsTokenPermissions {
switch cfg.TokenPermissionMode {
case repo_model.ActionsTokenPermissionModeRestricted:
return repo_model.MakeRestrictedPermissions()
case repo_model.ActionsTokenPermissionModePermissive:
return repo_model.MakeActionsTokenPermissions(perm.AccessModeWrite)
default:
return repo_model.MakeActionsTokenPermissions(perm.AccessModeNone)
}
}
// GetMaxTokenPermissions returns the maximum allowed permissions
func (cfg *OwnerActionsConfig) GetMaxTokenPermissions() repo_model.ActionsTokenPermissions {
if cfg.MaxTokenPermissions != nil {
return *cfg.MaxTokenPermissions
}
// Default max is write for everything
return repo_model.MakeActionsTokenPermissions(perm.AccessModeWrite)
}
// ClampPermissions ensures that the given permissions don't exceed the maximum
func (cfg *OwnerActionsConfig) ClampPermissions(perms repo_model.ActionsTokenPermissions) repo_model.ActionsTokenPermissions {
maxPerms := cfg.GetMaxTokenPermissions()
return repo_model.ClampActionsTokenPermissions(perms, maxPerms)
}
+132 -123
View File
@@ -7,7 +7,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"strings" "strings"
"time" "time"
@@ -16,13 +15,13 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/nektos/act/pkg/jobparser"
"xorm.io/builder" "xorm.io/builder"
) )
@@ -30,7 +29,7 @@ import (
type ActionRun struct { type ActionRun struct {
ID int64 ID int64
Title string Title string
RepoID int64 `xorm:"index unique(repo_index)"` RepoID int64 `xorm:"unique(repo_index) index(repo_concurrency)"`
Repo *repo_model.Repository `xorm:"-"` Repo *repo_model.Repository `xorm:"-"`
OwnerID int64 `xorm:"index"` OwnerID int64 `xorm:"index"`
WorkflowID string `xorm:"index"` // the name of workflow file WorkflowID string `xorm:"index"` // the name of workflow file
@@ -49,6 +48,9 @@ type ActionRun struct {
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
Status Status `xorm:"index"` Status Status `xorm:"index"`
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
RawConcurrency string // raw concurrency
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"`
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
// Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0 // Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0
Started timeutil.TimeStamp Started timeutil.TimeStamp
Stopped timeutil.TimeStamp Stopped timeutil.TimeStamp
@@ -67,14 +69,14 @@ func (run *ActionRun) HTMLURL() string {
if run.Repo == nil { if run.Repo == nil {
return "" return ""
} }
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.Index) return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.ID)
} }
func (run *ActionRun) Link() string { func (run *ActionRun) Link() string {
if run.Repo == nil { if run.Repo == nil {
return "" return ""
} }
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.ID)
} }
func (run *ActionRun) WorkflowLink() string { func (run *ActionRun) WorkflowLink() string {
@@ -102,8 +104,17 @@ func (run *ActionRun) PrettyRef() string {
return refName.ShortName() return refName.ShortName()
} }
// RefTooltip return a tooltop of run's ref. For pull request, it's the title of the PR, otherwise it's the ShortName.
func (run *ActionRun) RefTooltip() string {
payload, err := run.GetPullRequestEventPayload()
if err == nil && payload != nil && payload.PullRequest != nil {
return payload.PullRequest.Title
}
return git.RefName(run.Ref).ShortName()
}
// LoadAttributes load Repo TriggerUser if not loaded // LoadAttributes load Repo TriggerUser if not loaded
func (run *ActionRun) LoadAttributes(ctx context.Context) error { func (run *ActionRun) LoadAttributes(ctx context.Context) (err error) {
if run == nil { if run == nil {
return nil return nil
} }
@@ -117,11 +128,10 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error {
} }
if run.TriggerUser == nil { if run.TriggerUser == nil {
u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID) run.TriggerUserID, run.TriggerUser, err = user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
if err != nil { if err != nil {
return err return err
} }
run.TriggerUser = u
} }
return nil return nil
@@ -141,7 +151,11 @@ func (run *ActionRun) LoadRepo(ctx context.Context) error {
} }
func (run *ActionRun) Duration() time.Duration { func (run *ActionRun) Duration() time.Duration {
return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration d := calculateDuration(run.Started, run.Stopped, run.Status, run.Updated) + run.PreviousDuration
if d < 0 {
return 0
}
return d
} }
func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) { func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
@@ -156,7 +170,7 @@ func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
} }
func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) { func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) {
if run.Event.IsPullRequest() { if run.Event.IsPullRequest() || run.Event.IsPullRequestReview() {
var payload api.PullRequestPayload var payload api.PullRequestPayload
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
return nil, err return nil, err
@@ -181,30 +195,35 @@ func (run *ActionRun) IsSchedule() bool {
return run.ScheduleID > 0 return run.ScheduleID > 0
} }
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { // UpdateRepoRunsNumbers updates the number of runs and closed runs of a repository.
_, err := db.GetEngine(ctx).ID(repo.ID). // Callers MUST invoke this from outside any transaction that has X-locked action_run rows for the same repo, otherwise, transaction deadlock
NoAutoTime(). func UpdateRepoRunsNumbers(ctx context.Context, repoID int64) {
Cols("num_action_runs", "num_closed_action_runs"). if db.InTransaction(ctx) {
SetExpr("num_action_runs", setting.PanicInDevOrTesting("UpdateRepoRunsNumbers must not be called inside a transaction")
builder.Select("count(*)").From("action_run"). }
Where(builder.Eq{"repo_id": repo.ID}),
). e := db.GetEngine(ctx)
SetExpr("num_closed_action_runs",
builder.Select("count(*)").From("action_run"). numActionRuns, err := e.Where("repo_id = ?", repoID).Count(new(ActionRun))
Where(builder.Eq{ if err != nil {
"repo_id": repo.ID, log.Error("UpdateRepoRunsNumbers count num_action_runs for repo %d: %v", repoID, err)
}.And( return
builder.In("status", }
StatusSuccess,
StatusFailure, numClosedActionRuns, err := e.Where("repo_id = ?", repoID).
StatusCancelled, In("status", StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped).
StatusSkipped, Count(new(ActionRun))
), if err != nil {
), log.Error("UpdateRepoRunsNumbers count num_closed_action_runs for repo %d: %v", repoID, err)
), return
). }
Update(repo)
return err if _, err := e.ID(repoID).Cols("num_action_runs", "num_closed_action_runs").NoAutoTime().Update(&repo_model.Repository{
NumActionRuns: int(numActionRuns),
NumClosedActionRuns: int(numClosedActionRuns),
}); err != nil {
log.Error("UpdateRepoRunsNumbers update repo %d: %v", repoID, err)
}
} }
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event. // CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
@@ -239,6 +258,19 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
return cancelledJobs, err return cancelledJobs, err
} }
cjs, err := CancelJobs(ctx, jobs)
if err != nil {
return cancelledJobs, err
}
cancelledJobs = append(cancelledJobs, cjs...)
}
// Return nil to indicate successful cancellation of all running and waiting jobs.
return cancelledJobs, nil
}
func CancelJobs(ctx context.Context, jobs []*ActionRunJob) ([]*ActionRunJob, error) {
cancelledJobs := make([]*ActionRunJob, 0, len(jobs))
// Iterate over each job and attempt to cancel it. // Iterate over each job and attempt to cancel it.
for _, job := range jobs { for _, job := range jobs {
// Skip jobs that are already in a terminal state (completed, cancelled, etc.). // Skip jobs that are already in a terminal state (completed, cancelled, etc.).
@@ -258,9 +290,10 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
return cancelledJobs, err return cancelledJobs, err
} }
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again. // If the update affected 0 rows, it means the job has changed in the meantime
if n == 0 { if n == 0 {
return cancelledJobs, errors.New("job has changed, try again") log.Error("Failed to cancel job %d because it has changed", job.ID)
continue
} }
cancelledJobs = append(cancelledJobs, job) cancelledJobs = append(cancelledJobs, job)
@@ -272,85 +305,17 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil { if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
return cancelledJobs, err return cancelledJobs, err
} }
cancelledJobs = append(cancelledJobs, job) updatedJob, err := GetRunJobByRunAndID(ctx, job.RunID, job.ID)
if err != nil {
return cancelledJobs, fmt.Errorf("get job: %w", err)
} }
cancelledJobs = append(cancelledJobs, updatedJob)
} }
// Return nil to indicate successful cancellation of all running and waiting jobs. // Return nil to indicate successful cancellation of all running and waiting jobs.
return cancelledJobs, nil return cancelledJobs, nil
} }
// InsertRun inserts a run
// The title will be cut off at 255 characters if it's longer than 255 characters.
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
return db.WithTx(ctx, func(ctx context.Context) error {
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
if err != nil {
return err
}
run.Index = index
run.Title = util.EllipsisDisplayString(run.Title, 255)
if err := db.Insert(ctx, run); err != nil {
return err
}
if run.Repo == nil {
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err
}
runJobs := make([]*ActionRunJob, 0, len(jobs))
var hasWaiting bool
for _, v := range jobs {
id, job := v.Job()
needs := job.Needs()
if err := v.SetJob(id, job.EraseNeeds()); err != nil {
return err
}
payload, _ := v.Marshal()
status := StatusWaiting
if len(needs) > 0 || run.NeedApproval {
status = StatusBlocked
} else {
hasWaiting = true
}
job.Name = util.EllipsisDisplayString(job.Name, 255)
runJobs = append(runJobs, &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
IsForkPullRequest: run.IsForkPullRequest,
Name: job.Name,
WorkflowPayload: payload,
JobID: id,
Needs: needs,
RunsOn: job.RunsOn(),
Status: status,
})
}
if err := db.Insert(ctx, runJobs); err != nil {
return err
}
// if there is a job in the waiting status, increase tasks version.
if hasWaiting {
if err := IncreaseTaskVersion(ctx, run.OwnerID, run.RepoID); err != nil {
return err
}
}
return nil
})
}
func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) { func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) {
var run ActionRun var run ActionRun
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", runID, repoID).Get(&run) has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", runID, repoID).Get(&run)
@@ -363,16 +328,16 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er
return &run, nil return &run, nil
} }
func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) { func GetRunByRepoAndIndex(ctx context.Context, repoID, runIndex int64) (*ActionRun, error) {
run := &ActionRun{ run := &ActionRun{
RepoID: repoID, RepoID: repoID,
Index: index, Index: runIndex,
} }
has, err := db.GetEngine(ctx).Get(run) has, err := db.GetEngine(ctx).Get(run)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, fmt.Errorf("run with index %d %d: %w", repoID, index, util.ErrNotExist) return nil, fmt.Errorf("run with repo_id %d and index %d: %w", repoID, runIndex, util.ErrNotExist)
} }
return run, nil return run, nil
@@ -426,19 +391,63 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
// It's impossible that the run is not found, since Gitea never deletes runs. // It's impossible that the run is not found, since Gitea never deletes runs.
} }
if run.Status != 0 || slices.Contains(cols, "status") {
if run.RepoID == 0 {
setting.PanicInDevOrTesting("RepoID should not be 0")
}
if err = run.LoadRepo(ctx); err != nil {
return err
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err
}
}
return nil return nil
} }
type ActionRunIndex db.ResourceIndex type ActionRunIndex db.ResourceIndex
func GetConcurrentRunsAndJobs(ctx context.Context, repoID int64, concurrencyGroup string, status []Status) ([]*ActionRun, []*ActionRunJob, error) {
runs, err := db.Find[ActionRun](ctx, &FindRunOptions{
RepoID: repoID,
ConcurrencyGroup: concurrencyGroup,
Status: status,
})
if err != nil {
return nil, nil, fmt.Errorf("find runs: %w", err)
}
jobs, err := db.Find[ActionRunJob](ctx, &FindRunJobOptions{
RepoID: repoID,
ConcurrencyGroup: concurrencyGroup,
Statuses: status,
})
if err != nil {
return nil, nil, fmt.Errorf("find jobs: %w", err)
}
return runs, jobs, nil
}
func CancelPreviousJobsByRunConcurrency(ctx context.Context, actionRun *ActionRun) ([]*ActionRunJob, error) {
if actionRun.ConcurrencyGroup == "" {
return nil, nil
}
var jobsToCancel []*ActionRunJob
statusFindOption := []Status{StatusWaiting, StatusBlocked}
if actionRun.ConcurrencyCancel {
statusFindOption = append(statusFindOption, StatusRunning)
}
runs, jobs, err := GetConcurrentRunsAndJobs(ctx, actionRun.RepoID, actionRun.ConcurrencyGroup, statusFindOption)
if err != nil {
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
}
jobsToCancel = append(jobsToCancel, jobs...)
// cancel runs in the same concurrency group
for _, run := range runs {
if run.ID == actionRun.ID {
continue
}
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
RunID: run.ID,
})
if err != nil {
return nil, fmt.Errorf("find run %d jobs: %w", run.ID, err)
}
jobsToCancel = append(jobsToCancel, jobs...)
}
return CancelJobs(ctx, jobsToCancel)
}
+99 -7
View File
@@ -11,30 +11,56 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/actions/jobparser"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
// MaxJobNumPerRun is the maximum number of jobs in a single run.
// https://docs.github.com/en/actions/reference/limits#existing-system-limits
// TODO: check this limit when creating jobs
const MaxJobNumPerRun = 256
// ActionRunJob represents a job of a run // ActionRunJob represents a job of a run
type ActionRunJob struct { type ActionRunJob struct {
ID int64 ID int64
RunID int64 `xorm:"index"` RunID int64 `xorm:"index"`
Run *ActionRun `xorm:"-"` Run *ActionRun `xorm:"-"`
RepoID int64 `xorm:"index"` RepoID int64 `xorm:"index(repo_concurrency)"`
Repo *repo_model.Repository `xorm:"-"` Repo *repo_model.Repository `xorm:"-"`
OwnerID int64 `xorm:"index"` OwnerID int64 `xorm:"index"`
CommitSHA string `xorm:"index"` CommitSHA string `xorm:"index"`
IsForkPullRequest bool IsForkPullRequest bool
Name string `xorm:"VARCHAR(255)"` Name string `xorm:"VARCHAR(255)"`
Attempt int64 Attempt int64
// WorkflowPayload is act/jobparser.SingleWorkflow for act/jobparser.Parse
// it should contain exactly one job with global workflow fields for this model
WorkflowPayload []byte WorkflowPayload []byte
JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id
Needs []string `xorm:"JSON TEXT"` Needs []string `xorm:"JSON TEXT"`
RunsOn []string `xorm:"JSON TEXT"` RunsOn []string `xorm:"JSON TEXT"`
TaskID int64 // the latest task of the job TaskID int64 // the latest task of the job
Status Status `xorm:"index"` Status Status `xorm:"index"`
RawConcurrency string // raw concurrency from job YAML's "concurrency" section
// IsConcurrencyEvaluated is only valid/needed when this job's RawConcurrency is not empty.
// If RawConcurrency can't be evaluated (e.g. depend on other job's outputs or have errors), this field will be false.
// If RawConcurrency has been successfully evaluated, this field will be true, ConcurrencyGroup and ConcurrencyCancel are also set.
IsConcurrencyEvaluated bool
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"` // evaluated concurrency.group
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"` // evaluated concurrency.cancel-in-progress
// TokenPermissions stores the explicit permissions from workflow/job YAML (no org/repo clamps applied).
// Org/repo clamps are enforced when the token is used at runtime.
// It is JSON-encoded repo_model.ActionsTokenPermissions and may be empty if not specified.
TokenPermissions *repo_model.ActionsTokenPermissions `xorm:"JSON TEXT"`
Started timeutil.TimeStamp Started timeutil.TimeStamp
Stopped timeutil.TimeStamp Stopped timeutil.TimeStamp
Created timeutil.TimeStamp `xorm:"created"` Created timeutil.TimeStamp `xorm:"created"`
@@ -46,7 +72,7 @@ func init() {
} }
func (job *ActionRunJob) Duration() time.Duration { func (job *ActionRunJob) Duration() time.Duration {
return calculateDuration(job.Started, job.Stopped, job.Status) return calculateDuration(job.Started, job.Stopped, job.Status, job.Updated)
} }
func (job *ActionRunJob) LoadRun(ctx context.Context) error { func (job *ActionRunJob) LoadRun(ctx context.Context) error {
@@ -84,13 +110,43 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
return job.Run.LoadAttributes(ctx) return job.Run.LoadAttributes(ctx)
} }
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) { // ParseJob parses the job structure from the ActionRunJob.WorkflowPayload
func (job *ActionRunJob) ParseJob() (*jobparser.Job, error) {
// job.WorkflowPayload is a SingleWorkflow created from an ActionRun's workflow, which exactly contains this job's YAML definition.
// Ideally it shouldn't be called "Workflow", it is just a job with global workflow fields + trigger
parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload)
if err != nil {
return nil, fmt.Errorf("job %d single workflow: unable to parse: %w", job.ID, err)
} else if len(parsedWorkflows) != 1 {
return nil, fmt.Errorf("job %d single workflow: not single workflow", job.ID)
}
_, workflowJob := parsedWorkflows[0].Job()
if workflowJob == nil {
// it shouldn't happen, and since the callers don't check nil, so return an error instead of nil
return nil, util.ErrorWrap(util.ErrNotExist, "job %d single workflow: payload doesn't contain a job", job.ID)
}
return workflowJob, nil
}
func GetRunJobByRepoAndID(ctx context.Context, repoID, jobID int64) (*ActionRunJob, error) {
var job ActionRunJob var job ActionRunJob
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job) has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", jobID, repoID).Get(&job)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, fmt.Errorf("run job with id %d: %w", id, util.ErrNotExist) return nil, fmt.Errorf("run job with id %d: %w", jobID, util.ErrNotExist)
}
return &job, nil
}
func GetRunJobByRunAndID(ctx context.Context, runID, jobID int64) (*ActionRunJob, error) {
var job ActionRunJob
has, err := db.GetEngine(ctx).Where("id=? AND run_id=?", jobID, runID).Get(&job)
if err != nil {
return nil, err
} else if !has {
return nil, fmt.Errorf("run job with id %d: %w", jobID, util.ErrNotExist)
} }
return &job, nil return &job, nil
@@ -125,7 +181,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
return affected, nil return affected, nil
} }
if affected != 0 && slices.Contains(cols, "status") && job.Status.IsWaiting() { if slices.Contains(cols, "status") && job.Status.IsWaiting() {
// if the status of job changes to waiting again, increase tasks version. // if the status of job changes to waiting again, increase tasks version.
if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil { if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil {
return 0, err return 0, err
@@ -134,7 +190,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
if job.RunID == 0 { if job.RunID == 0 {
var err error var err error
if job, err = GetRunJobByID(ctx, job.ID); err != nil { if job, err = GetRunJobByRepoAndID(ctx, job.RepoID, job.ID); err != nil {
return 0, err return 0, err
} }
} }
@@ -197,3 +253,39 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
return StatusUnknown // it shouldn't happen return StatusUnknown // it shouldn't happen
} }
} }
func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob) (jobsToCancel []*ActionRunJob, _ error) {
if job.RawConcurrency == "" {
return nil, nil
}
if !job.IsConcurrencyEvaluated {
return nil, nil
}
if job.ConcurrencyGroup == "" {
return nil, nil
}
statusFindOption := []Status{StatusWaiting, StatusBlocked}
if job.ConcurrencyCancel {
statusFindOption = append(statusFindOption, StatusRunning)
}
runs, jobs, err := GetConcurrentRunsAndJobs(ctx, job.RepoID, job.ConcurrencyGroup, statusFindOption)
if err != nil {
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
}
jobs = slices.DeleteFunc(jobs, func(j *ActionRunJob) bool { return j.ID == job.ID })
jobsToCancel = append(jobsToCancel, jobs...)
// cancel runs in the same concurrency group
for _, run := range runs {
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
RunID: run.ID,
})
if err != nil {
return nil, fmt.Errorf("find run %d jobs: %w", run.ID, err)
}
jobsToCancel = append(jobsToCancel, jobs...)
}
return CancelJobs(ctx, jobsToCancel)
}
@@ -75,6 +75,7 @@ type FindRunJobOptions struct {
CommitSHA string CommitSHA string
Statuses []Status Statuses []Status
UpdatedBefore timeutil.TimeStamp UpdatedBefore timeutil.TimeStamp
ConcurrencyGroup string
} }
func (opts FindRunJobOptions) ToConds() builder.Cond { func (opts FindRunJobOptions) ToConds() builder.Cond {
@@ -94,6 +95,12 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
if opts.UpdatedBefore > 0 { if opts.UpdatedBefore > 0 {
cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore}) cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore})
} }
if opts.ConcurrencyGroup != "" {
if opts.RepoID == 0 {
panic("Invalid FindRunJobOptions: repo_id is required")
}
cond = cond.And(builder.Eq{"`action_run_job`.concurrency_group": opts.ConcurrencyGroup})
}
return cond return cond
} }
@@ -72,6 +72,7 @@ type FindRunOptions struct {
TriggerEvent webhook_module.HookEventType TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true Approved bool // not util.OptionalBool, it works only when it's true
Status []Status Status []Status
ConcurrencyGroup string
CommitSHA string CommitSHA string
} }
@@ -101,6 +102,12 @@ func (opts FindRunOptions) ToConds() builder.Cond {
if opts.CommitSHA != "" { if opts.CommitSHA != "" {
cond = cond.And(builder.Eq{"`action_run`.commit_sha": opts.CommitSHA}) cond = cond.And(builder.Eq{"`action_run`.commit_sha": opts.CommitSHA})
} }
if len(opts.ConcurrencyGroup) > 0 {
if opts.RepoID == 0 {
panic("Invalid FindRunOptions: repo_id is required")
}
cond = cond.And(builder.Eq{"`action_run`.concurrency_group": opts.ConcurrencyGroup})
}
return cond return cond
} }
@@ -5,10 +5,12 @@ package actions
import ( import (
"testing" "testing"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -27,9 +29,18 @@ func TestUpdateRepoRunsNumbers(t *testing.T) {
assert.Equal(t, 2, repo.NumClosedActionRuns) assert.Equal(t, 2, repo.NumClosedActionRuns)
// now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated // now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated
err = updateRepoRunsNumbers(t.Context(), repo) UpdateRepoRunsNumbers(t.Context(), repo.ID)
assert.NoError(t, err)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.Equal(t, 5, repo.NumActionRuns) assert.Equal(t, 5, repo.NumActionRuns)
assert.Equal(t, 3, repo.NumClosedActionRuns) assert.Equal(t, 3, repo.NumClosedActionRuns)
} }
func TestActionRun_Duration_NonNegative(t *testing.T) {
run := &ActionRun{
Started: timeutil.TimeStamp(100),
Stopped: timeutil.TimeStamp(200),
Status: StatusSuccess,
PreviousDuration: -time.Hour,
}
assert.Equal(t, time.Duration(0), run.Duration())
}
@@ -14,6 +14,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/shared/types" "code.gitea.io/gitea/models/shared/types"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@@ -61,6 +62,8 @@ type ActionRunner struct {
AgentLabels []string `xorm:"TEXT"` AgentLabels []string `xorm:"TEXT"`
// Store if this is a runner that only ever get one single job assigned // Store if this is a runner that only ever get one single job assigned
Ephemeral bool `xorm:"ephemeral NOT NULL DEFAULT false"` Ephemeral bool `xorm:"ephemeral NOT NULL DEFAULT false"`
// Store if this runner is disabled and should not pick up new jobs
IsDisabled bool `xorm:"is_disabled NOT NULL DEFAULT false"`
Created timeutil.TimeStamp `xorm:"created"` Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"` Updated timeutil.TimeStamp `xorm:"updated"`
@@ -173,6 +176,13 @@ func (r *ActionRunner) GenerateToken() (err error) {
return err return err
} }
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
runnerLabelSet := container.SetOf(r.AgentLabels...)
return runnerLabelSet.Contains(jobRunsOn...) // match all labels
}
func init() { func init() {
db.RegisterModel(&ActionRunner{}) db.RegisterModel(&ActionRunner{})
} }
@@ -191,6 +201,7 @@ type FindRunnerOptions struct {
Sort string Sort string
Filter string Filter string
IsOnline optional.Option[bool] IsOnline optional.Option[bool]
IsDisabled optional.Option[bool]
WithAvailable bool // not only runners belong to, but also runners can be used WithAvailable bool // not only runners belong to, but also runners can be used
} }
@@ -231,6 +242,10 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()}) cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
} }
} }
if opts.IsDisabled.Has() {
cond = cond.And(builder.Eq{"is_disabled": opts.IsDisabled.Value()})
}
return cond return cond
} }
@@ -289,6 +304,20 @@ func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
return err return err
} }
func SetRunnerDisabled(ctx context.Context, runner *ActionRunner, isDisabled bool) error {
if runner.IsDisabled == isDisabled {
return nil
}
return db.WithTx(ctx, func(ctx context.Context) error {
runner.IsDisabled = isDisabled
if err := UpdateRunner(ctx, runner, "is_disabled"); err != nil {
return err
}
return IncreaseTaskVersion(ctx, runner.OwnerID, runner.RepoID)
})
}
// DeleteRunner deletes a runner by given ID. // DeleteRunner deletes a runner by given ID.
func DeleteRunner(ctx context.Context, id int64) error { func DeleteRunner(ctx context.Context, id int64) error {
if _, err := GetRunnerByID(ctx, id); err != nil { if _, err := GetRunnerByID(ctx, id); err != nil {
+24 -26
View File
@@ -8,12 +8,13 @@ import (
"crypto/subtle" "crypto/subtle"
"errors" "errors"
"fmt" "fmt"
"strings"
"time" "time"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/actions/jobparser"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@@ -21,7 +22,6 @@ import (
runnerv1 "code.gitea.io/actions-proto-go/runner/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
lru "github.com/hashicorp/golang-lru/v2" lru "github.com/hashicorp/golang-lru/v2"
"github.com/nektos/act/pkg/jobparser"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"xorm.io/builder" "xorm.io/builder"
) )
@@ -77,7 +77,7 @@ func init() {
} }
func (task *ActionTask) Duration() time.Duration { func (task *ActionTask) Duration() time.Duration {
return calculateDuration(task.Started, task.Stopped, task.Status) return calculateDuration(task.Started, task.Stopped, task.Status, task.Updated)
} }
func (task *ActionTask) IsStopped() bool { func (task *ActionTask) IsStopped() bool {
@@ -114,7 +114,7 @@ func (task *ActionTask) GetRepoLink() string {
func (task *ActionTask) LoadJob(ctx context.Context) error { func (task *ActionTask) LoadJob(ctx context.Context) error {
if task.Job == nil { if task.Job == nil {
job, err := GetRunJobByID(ctx, task.JobID) job, err := GetRunJobByRepoAndID(ctx, task.RepoID, task.JobID)
if err != nil { if err != nil {
return err return err
} }
@@ -216,6 +216,20 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
return nil, errNotExist return nil, errNotExist
} }
func makeTaskStepDisplayName(step *jobparser.Step, limit int) (name string) {
if step.Name != "" {
name = step.Name // the step has an explicit name
} else {
// for unnamed step, its "String()" method tries to get a display name by its "name", "uses",
// "run" or "id" (last fallback), we add the "Run " prefix for unnamed steps for better display
// for multi-line "run" scripts, only use the first line to match GitHub's behavior
// https://github.com/actions/runner/blob/66800900843747f37591b077091dd2c8cf2c1796/src/Runner.Worker/Handlers/ScriptHandler.cs#L45-L58
runStr, _, _ := strings.Cut(strings.TrimSpace(step.Run), "\n")
name = "Run " + util.IfZero(strings.TrimSpace(runStr), step.String())
}
return util.EllipsisDisplayString(name, limit) // database column has a length limit
}
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) { func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)
if err != nil { if err != nil {
@@ -246,7 +260,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
var job *ActionRunJob var job *ActionRunJob
log.Trace("runner labels: %v", runner.AgentLabels) log.Trace("runner labels: %v", runner.AgentLabels)
for _, v := range jobs { for _, v := range jobs {
if isSubset(runner.AgentLabels, v.RunsOn) { if runner.CanMatchLabels(v.RunsOn) {
job = v job = v
break break
} }
@@ -278,13 +292,10 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
return nil, false, err return nil, false, err
} }
parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload) workflowJob, err := job.ParseJob()
if err != nil { if err != nil {
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err) return nil, false, fmt.Errorf("load job %d: %w", job.ID, err)
} else if len(parsedWorkflows) != 1 {
return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID)
} }
_, workflowJob := parsedWorkflows[0].Job()
if _, err := e.Insert(task); err != nil { if _, err := e.Insert(task); err != nil {
return nil, false, err return nil, false, err
@@ -298,9 +309,8 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if len(workflowJob.Steps) > 0 { if len(workflowJob.Steps) > 0 {
steps := make([]*ActionTaskStep, len(workflowJob.Steps)) steps := make([]*ActionTaskStep, len(workflowJob.Steps))
for i, v := range workflowJob.Steps { for i, v := range workflowJob.Steps {
name := util.EllipsisDisplayString(v.String(), 255)
steps[i] = &ActionTaskStep{ steps[i] = &ActionTaskStep{
Name: name, Name: makeTaskStepDisplayName(v, 255),
TaskID: task.ID, TaskID: task.ID,
Index: int64(i), Index: int64(i),
RepoID: task.RepoID, RepoID: task.RepoID,
@@ -378,6 +388,7 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task
} }
if _, err := UpdateRunJob(ctx, &ActionRunJob{ if _, err := UpdateRunJob(ctx, &ActionRunJob{
ID: task.JobID, ID: task.JobID,
RepoID: task.RepoID,
Status: task.Status, Status: task.Status,
Stopped: task.Stopped, Stopped: task.Stopped,
}, nil); err != nil { }, nil); err != nil {
@@ -439,6 +450,7 @@ func StopTask(ctx context.Context, taskID int64, status Status) error {
task.Stopped = now task.Stopped = now
if _, err := UpdateRunJob(ctx, &ActionRunJob{ if _, err := UpdateRunJob(ctx, &ActionRunJob{
ID: task.JobID, ID: task.JobID,
RepoID: task.RepoID,
Status: task.Status, Status: task.Status,
Stopped: task.Stopped, Stopped: task.Stopped,
}, nil); err != nil { }, nil); err != nil {
@@ -479,20 +491,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
Find(&tasks) Find(&tasks)
} }
func isSubset(set, subset []string) bool {
m := make(container.Set[string], len(set))
for _, v := range set {
m.Add(v)
}
for _, v := range subset {
if !m.Contains(v) {
return false
}
}
return true
}
func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp { func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 { if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
return timeutil.TimeStamp(0) return timeutil.TimeStamp(0)
@@ -14,7 +14,7 @@ import (
// ActionTaskStep represents a step of ActionTask // ActionTaskStep represents a step of ActionTask
type ActionTaskStep struct { type ActionTaskStep struct {
ID int64 ID int64
Name string `xorm:"VARCHAR(255)"` Name string `xorm:"VARCHAR(255)"` // the step name, for display purpose only, it will be truncated if it is too long
TaskID int64 `xorm:"index unique(task_index)"` TaskID int64 `xorm:"index unique(task_index)"`
Index int64 `xorm:"index unique(task_index)"` Index int64 `xorm:"index unique(task_index)"`
RepoID int64 `xorm:"index"` RepoID int64 `xorm:"index"`
@@ -28,7 +28,7 @@ type ActionTaskStep struct {
} }
func (step *ActionTaskStep) Duration() time.Duration { func (step *ActionTaskStep) Duration() time.Duration {
return calculateDuration(step.Started, step.Stopped, step.Status) return calculateDuration(step.Started, step.Stopped, step.Status, step.Updated)
} }
func init() { func init() {

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