mirror of
https://codeberg.org/forgejo/forgejo
synced 2025-10-19 08:00:46 +02:00
Compare commits
166 commits
v14.0.0-de
...
forgejo
Author | SHA1 | Date | |
---|---|---|---|
|
0a7e438e43 | ||
|
6726861e49 | ||
|
c54cfa3af8 | ||
|
3f5a01e42b | ||
|
3f1731a765 | ||
|
c22ec65856 | ||
|
bc0568b3ee | ||
|
e20c674560 | ||
|
5b13c6e024 | ||
|
fe4a2ae7c1 | ||
|
affadc359e | ||
|
8ed95dc4c6 | ||
|
e56fdf1ec5 | ||
|
52454651ea | ||
|
4d7b59a9f9 | ||
|
ee7a4a827a | ||
|
6db2e23078 | ||
|
46e491f05e | ||
|
d2604ab313 | ||
|
ad70b09eea | ||
|
c5b292b635 | ||
|
b818a028a1 | ||
|
87380722b5 | ||
|
e777b59673 | ||
|
d2d388bdcd | ||
|
5494d8b3cd |
||
|
ed0bec9aa9 | ||
|
1c7e189dd0 | ||
|
626ff29545 | ||
|
558e9bdf5f |
||
|
c3d64da23c | ||
|
709c315bc4 |
||
|
49bcd792cd |
||
|
a0be0f22fc |
||
|
56d9b4b14d | ||
|
82ad5189fe | ||
|
adc01a086a | ||
|
67a9b80c51 | ||
|
c009f450a6 | ||
|
906e2e7c4a | ||
|
78d92aafd7 | ||
|
8eb8f49581 | ||
|
d0a6f93f9e | ||
|
0a8b6a9429 | ||
|
92684b6208 | ||
|
15cb5cc6c1 | ||
|
19c9f6194b | ||
|
a80f8f9d01 | ||
|
1a93e211e4 | ||
|
79f6f8e508 | ||
|
ee34e3a317 | ||
|
f200d53eb1 | ||
|
e9528ec4a8 | ||
|
e972a5bf49 | ||
|
e13da8aae3 | ||
|
e588a8642b | ||
|
079c73635c | ||
|
688a83e405 | ||
|
edc6072da2 | ||
|
03d77fd465 | ||
|
4841b310d8 | ||
|
f250195256 | ||
|
9a35b6ba77 | ||
|
220c8d173b | ||
|
188810c9e9 | ||
|
4adec07103 | ||
|
89469d14e9 | ||
|
b529b80132 | ||
|
39f389eb17 | ||
|
2189b35545 | ||
|
ee41e6eead | ||
|
71257fff2c | ||
|
419eb0a4a2 | ||
|
84e9b566a0 | ||
|
2b4754c1d7 | ||
|
07bff8143e | ||
|
fc9db11c56 | ||
|
a5abeba442 | ||
|
c79793be88 | ||
|
5281ac76cb | ||
|
3897001cec | ||
|
815857cfc0 | ||
|
08cb52a768 | ||
|
d5742c31fb |
||
|
a28526a521 | ||
|
098cb0786d | ||
|
9db4e2f38f | ||
|
1fa23c219f | ||
|
f80a5e7445 | ||
|
68bf916acd | ||
|
563291579e | ||
|
94c068e91e | ||
|
b5e9b26ae0 | ||
|
5ed86bf257 | ||
|
5ac21c69aa | ||
|
2ba873cbff | ||
|
16e415ebf1 | ||
|
7ce74a31e2 | ||
|
b71df03e35 | ||
|
6610eb1dbf | ||
|
86dbec6b57 | ||
|
643cd4fa6e | ||
|
aa9006bca0 | ||
|
abd69183ea | ||
|
fcf2b0a187 | ||
|
068e318629 | ||
|
f8d25228ce | ||
|
fd08eba8d2 | ||
|
f2570811a4 | ||
|
1e113fd8dc | ||
|
0a02ffd355 | ||
|
9a29241cde | ||
|
02ea77c6a0 | ||
|
25ba696b6a | ||
|
152b98da90 | ||
|
877d4ae833 | ||
|
c434b963b4 | ||
|
0cf3030d7b | ||
|
112ad8d1da |
||
|
f87b76160e | ||
|
cb7d2057ce | ||
|
8c6f572615 |
||
|
8bd5169c5f | ||
|
957a76df3b | ||
|
32c187e5bb | ||
|
25233f8aad | ||
|
c37ccaeff1 | ||
|
ac6555f3fd | ||
|
cbf13d73d4 | ||
|
5c9211f381 | ||
|
d24ee72e42 | ||
|
8db9870295 | ||
|
8435a8eb16 | ||
|
bfee082c01 | ||
|
ec172f2d0e | ||
|
796f129b8d | ||
|
10e976699a | ||
|
7b1ed4d723 | ||
|
e4779f32c5 | ||
|
7530e8a941 | ||
|
6d1818ffdb | ||
|
0145621e8d | ||
|
1cd0c5e99b | ||
|
dcd431b0d4 | ||
|
7939521a10 | ||
|
187ad99f3c | ||
|
ee4f9b02f9 | ||
|
debf12f6c5 | ||
|
5687a8ef65 | ||
|
710600f459 | ||
|
52925c44c7 | ||
|
f30fc08468 | ||
|
98073ac28d | ||
|
c271c73e53 | ||
|
16e66254c6 | ||
|
b470c6c454 | ||
|
cd7a8f4546 |
||
|
0b923a03b4 | ||
|
0a18c04a4b | ||
|
c8ec9bc2cc | ||
|
5da9c6ce64 | ||
|
fd27fceacb | ||
|
64cfb3580d | ||
|
152fd11bc6 | ||
|
42097bc0cb | ||
|
b29ab34338 |
993 changed files with 14000 additions and 4302 deletions
|
@ -11,7 +11,7 @@ include_file = ["main.go"]
|
|||
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||
exclude_dir = [
|
||||
"models/fixtures",
|
||||
"models/migrations/fixtures",
|
||||
"models/gitea_migrations/fixtures",
|
||||
"modules/avatar/identicon/testdata",
|
||||
"modules/avatar/testdata",
|
||||
"modules/git/tests",
|
||||
|
|
|
@ -18,9 +18,11 @@ forgejo.org/models/auth
|
|||
|
||||
forgejo.org/models/db
|
||||
TruncateBeans
|
||||
TruncateBeansCascade
|
||||
InTransaction
|
||||
DumpTables
|
||||
GetTableNames
|
||||
extendBeansForCascade
|
||||
|
||||
forgejo.org/models/dbfs
|
||||
file.renameTo
|
||||
|
@ -32,6 +34,9 @@ forgejo.org/models/forgejo/semver
|
|||
SetVersionString
|
||||
SetVersion
|
||||
|
||||
forgejo.org/models/forgejo_migrations
|
||||
resetMigrations
|
||||
|
||||
forgejo.org/models/git
|
||||
RemoveDeletedBranchByID
|
||||
|
||||
|
@ -224,6 +229,12 @@ forgejo.org/services/context
|
|||
forgejo.org/services/federation
|
||||
FollowRemoteActor
|
||||
|
||||
forgejo.org/services/mailer
|
||||
ActionCloseIssueByCommit.isActionAdditionalData
|
||||
|
||||
forgejo.org/services/notify
|
||||
UnregisterNotifier
|
||||
|
||||
forgejo.org/services/repository
|
||||
IsErrForkAlreadyExist
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Gitea DevContainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.24-bullseye",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:2.0-bullseye",
|
||||
"features": {
|
||||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
|
|
|
@ -17,7 +17,7 @@ runs:
|
|||
apt-get -q install -qq -y zstd
|
||||
|
||||
- name: "Set up Go using setup-go"
|
||||
uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
id: go-version
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: lxc-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- id: forgejo
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.0.4
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
# root is used for testing, allow it
|
||||
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
@ -43,11 +43,11 @@ jobs:
|
|||
repository="${{ github.repository }}"
|
||||
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v4
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
container:
|
||||
image: data.forgejo.org/oci/node:22-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
show-progress: 'false'
|
||||
|
|
|
@ -58,10 +58,10 @@ jobs:
|
|||
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
|
||||
options: --tmpfs /bitnami/postgresql
|
||||
cacher:
|
||||
image: registry.redict.io/redict:7.3.5-scratch
|
||||
image: registry.redict.io/redict:7.3.6-scratch
|
||||
options: --tmpfs /data:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ inputs.repository }}
|
||||
ref: ${{ inputs.ref }}
|
||||
|
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
runs-on: lxc-bookworm
|
||||
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- name: copy & sign
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.4.1
|
||||
|
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
container:
|
||||
image: 'data.forgejo.org/oci/ci:1'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- uses: https://data.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
container:
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- name: event
|
||||
run: |
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: false
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: data.forgejo.org/renovate/renovate:41.122.3
|
||||
image: data.forgejo.org/renovate/renovate:41.146.0
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
|
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git 2.34.1 and git-lfs 3.0.2
|
||||
uses: ./.forgejo/workflows-composite/install-minimum-git-version
|
||||
|
@ -53,7 +53,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git 2.34.1 and git-lfs 3.0.2
|
||||
uses: ./.forgejo/workflows-composite/install-minimum-git-version
|
||||
|
@ -85,7 +85,7 @@ jobs:
|
|||
MARIADB_DATABASE: testgitea
|
||||
options: --tmpfs /var/lib/mysql:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check lint-swagger fmt-check swagger-validate' # ensure the "go-licenses" make target runs
|
||||
|
@ -33,7 +33,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
|
@ -77,7 +77,7 @@ jobs:
|
|||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -104,7 +104,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/playwright:latest'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 20
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
|
@ -126,7 +126,7 @@ jobs:
|
|||
echo "all=1" >> "$GITHUB_OUTPUT"
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: https://data.forgejo.org/tj-actions/changed-files@v46
|
||||
uses: https://data.forgejo.org/tj-actions/changed-files@v47
|
||||
with:
|
||||
separator: '\n'
|
||||
- run: |
|
||||
|
@ -172,7 +172,7 @@ jobs:
|
|||
image: ${{ matrix.cacher.image }}
|
||||
options: ${{ matrix.cacher.options }}
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -205,7 +205,7 @@ jobs:
|
|||
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 --disable-log-bin
|
||||
options: --tmpfs /bitnami/mysql/data:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -233,6 +233,7 @@ jobs:
|
|||
options: --tmpfs /bitnami/minio/data
|
||||
ldap:
|
||||
image: data.forgejo.org/oci/forgejo-test-openldap:1
|
||||
options: --memory 500M
|
||||
pgsql:
|
||||
image: data.forgejo.org/oci/bitnami/postgresql:16
|
||||
env:
|
||||
|
@ -242,7 +243,7 @@ jobs:
|
|||
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
|
||||
options: --tmpfs /bitnami/postgresql
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -264,7 +265,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
|
@ -292,7 +293,7 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- run: su forgejo -c 'make security-check'
|
||||
|
|
|
@ -94,6 +94,7 @@ linters:
|
|||
- all
|
||||
testifylint:
|
||||
disable:
|
||||
- error-is-as
|
||||
- go-require
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
@ -119,7 +120,7 @@ linters:
|
|||
- errcheck
|
||||
- gocyclo
|
||||
- gosec
|
||||
path: models/migrations/v
|
||||
path: models/gitea_migrations/v
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: cmd
|
||||
|
|
|
@ -5,6 +5,7 @@ branch-find-version: 'v(?P<version>\d+\.\d+)/forgejo'
|
|||
branch-to-version: '${version}.0'
|
||||
branch-from-version: 'v%[1]d.%[2]d/forgejo'
|
||||
tag-from-version: 'v%[1]d.%[2]d.%[3]d'
|
||||
supported-release-count: 3
|
||||
branch-known:
|
||||
- 'v7.0/forgejo'
|
||||
cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"'
|
||||
|
|
|
@ -38,6 +38,10 @@ routers/.* @gusted
|
|||
options/locale/.* @0ko
|
||||
options/locale_next/.* @0ko
|
||||
|
||||
# Personal interest
|
||||
# lint-locale-usage
|
||||
build/lint-locale-usage/.* @fogti
|
||||
models/unit/.* @fogti
|
||||
services/migrations/lint-locale-usage/.* @fogti
|
||||
|
||||
# Personal interest
|
||||
.*/webhook.* @oliverpool
|
||||
|
|
32
Makefile
32
Makefile
|
@ -37,17 +37,17 @@ endif
|
|||
XGO_VERSION := go-1.21.x
|
||||
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.3.0 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.3.1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.4.1 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.36.0 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.38.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@41.122.3 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@41.146.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
|
||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||
|
@ -287,14 +287,14 @@ show-version-api: verify-version
|
|||
.PHONY: compute-go-test-packages
|
||||
compute-go-test-packages:
|
||||
ifeq ($(HAS_GO), yes)
|
||||
$(eval GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...)))
|
||||
$(eval GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/gitea_migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations_legacy/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...)))
|
||||
endif
|
||||
|
||||
# Target to compute MIGRATION_PACKAGES - only runs when needed
|
||||
.PHONY: compute-migration-packages
|
||||
compute-migration-packages:
|
||||
ifeq ($(HAS_GO), yes)
|
||||
$(eval MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...))
|
||||
$(eval MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/gitea_migrations/... forgejo.org/models/forgejo_migrations_legacy/... forgejo.org/models/forgejo_migrations/...))
|
||||
endif
|
||||
|
||||
###
|
||||
|
@ -432,7 +432,7 @@ lint: lint-frontend lint-backend
|
|||
lint-fix: lint-frontend-fix lint-backend-fix
|
||||
|
||||
.PHONY: lint-frontend
|
||||
lint-frontend: lint-js lint-css
|
||||
lint-frontend: lint-js tsc lint-css
|
||||
|
||||
.PHONY: lint-frontend-fix
|
||||
lint-frontend-fix: lint-js-fix lint-css-fix
|
||||
|
@ -475,7 +475,7 @@ lint-locale:
|
|||
|
||||
.PHONY: lint-locale-usage
|
||||
lint-locale-usage:
|
||||
$(GO) run ./build/lint-locale-usage --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt
|
||||
$(GO) run ./build/lint-locale-usage/bin --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt
|
||||
|
||||
.PHONY: lint-md
|
||||
lint-md: node_modules
|
||||
|
@ -514,7 +514,11 @@ lint-disposable-emails-fix:
|
|||
|
||||
.PHONY: security-check
|
||||
security-check:
|
||||
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
$(GO) run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
|
||||
.PHONY: tsc
|
||||
tsc: node_modules
|
||||
npx tsc --noEmit
|
||||
|
||||
###
|
||||
# Development and testing targets
|
||||
|
@ -774,7 +778,7 @@ migrations.individual.mysql.test: $(GO_SOURCES) | compute-migration-packages
|
|||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages
|
||||
|
@ -784,7 +788,7 @@ migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages
|
|||
|
||||
.PHONY: migrations.individual.pgsql.test\#%
|
||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-migration-packages
|
||||
|
@ -794,7 +798,7 @@ migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-m
|
|||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
|
||||
e2e.mysql.test: $(GO_SOURCES)
|
||||
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.mysql.test
|
||||
|
|
|
@ -130,6 +130,10 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
|
|||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3334): <!--number 3334 --><!--number--><!--description Added support for the `workflow_dispatch` workflow trigger-->added support for the [`workflow_dispatch` trigger](https://forgejo.org/docs/v8.0/user/actions/#onworkflow_dispatch) in Forgejo Actions.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3307): <!--number 3307 --><!--number--><!--description Support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login using the OpenID Connect authentication source.-->support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login using the OpenID Connect authentication source.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3139): <!--number 3139 --><!--number--><!--description Allow hiding auto generated release archives-->allow hiding auto generated release archives.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0:
|
||||
- [`1e983e7`](https://github.com/alecthomas/chroma/commit/1e983e7) lexers/cue: support CUE attributes ([#​961](https://github.com/alecthomas/chroma/issues/961))
|
||||
- [`9347b55`](https://github.com/alecthomas/chroma/commit/9347b55) Add Gleam syntax highlighting ([#​959](https://github.com/alecthomas/chroma/issues/959))
|
||||
- [`2580aaa`](https://github.com/alecthomas/chroma/commit/2580aaa) Add Bazel bzlmod support into Python lexer ([#​947](https://github.com/alecthomas/chroma/issues/947))
|
||||
- **Bug fixes**
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4732) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4715)): <!--number 4732 --><!--number--><!--description -->Show the AGit label on merged pull requests.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4689) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4687)): <!--number 4689 --><!--number--><!--description -->Fixed: issue state change via the API is not idempotent.<!--description-->
|
||||
|
@ -146,6 +150,9 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
|
|||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3442): <!--number 3442 --><!--number--><!--description Save updated empty comments instead of skipping the update silently, [which prevented the removal of attachments of such comments](https://codeberg.org/forgejo/forgejo/issues/3424).-->Fixed: it is not possible to remove attachments from an empty comment.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3430): <!--number 3430 --><!--number--><!--description Fixed a bug where the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints were using a hardcoded "master" branch for the wiki, rather than the branch they really use.-->Fixed: the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints is using a hardcoded "master" branch for the wiki, rather than the branch they really use.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3379): <!--number 3379 --><!--number--><!--description -->Fixed: using the API to search for users, the results are not paged by default an the default paging limits are not respected.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0:
|
||||
- [`736c0ea`](https://github.com/alecthomas/chroma/commit/736c0ea) Typescript: Several fixes ([#​952](https://github.com/alecthomas/chroma/issues/952))
|
||||
- [`e5c25d0`](https://github.com/alecthomas/chroma/commit/e5c25d0) Org: Keep all newlines ([#​951](https://github.com/alecthomas/chroma/issues/951))
|
||||
- **Localization**
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4661) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4568)): <!--number 4661 --><!--number--><!--description -->24 July updates<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4565) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4451)): <!--number 4565 --><!--number--><!--description -->19 July updates<!--description-->
|
||||
|
|
62
assets/go-licenses.json
generated
62
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
@ -74,7 +74,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error)
|
|||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/gitea_migrations/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string
|
|||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
return mainOptions, subCmd, subArgs
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
|
|
|
@ -23,12 +23,6 @@ themes.names.
|
|||
# services/context/context.go
|
||||
relativetime.
|
||||
|
||||
# templates/mail/issue/default.tmpl: $.locale.Tr
|
||||
mail.issue.in_tree_path
|
||||
|
||||
# templates/package/metadata/arch.tmpl: $.locale.Tr
|
||||
packages.details.license
|
||||
|
||||
# templates/repo/issue/view_content.tmpl: indirection via $closeTranslationKey
|
||||
repo.issues.close
|
||||
repo.pulls.close
|
||||
|
@ -48,7 +42,3 @@ projects.type-3.display_name
|
|||
# templates/repo/settings/webhook/link_menu.tmpl, templates/webhook/new.tmpl: repo.settings.web_hook_name_
|
||||
# tests/integration/repo_archive_text_test.go
|
||||
repo.settings.
|
||||
|
||||
# services/migrations/migrate.go: messenger calls
|
||||
# ToDo: give them a unique prefix
|
||||
repo.migrate.
|
||||
|
|
177
build/lint-locale-usage/bin/handle-go.go
Normal file
177
build/lint-locale-usage/bin/handle-go.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
goParser "go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
lluUnit "forgejo.org/models/unit/lint-locale-usage"
|
||||
lluMigrate "forgejo.org/services/migrations/lint-locale-usage"
|
||||
)
|
||||
|
||||
// the `Handle*File` functions follow the following calling convention:
|
||||
// * `fname` is the name of the input file
|
||||
// * `src` is either `nil` (then the function invokes `ReadFile` to read the file)
|
||||
// or the contents of the file as {`[]byte`, or a `string`}
|
||||
|
||||
func HandleGoFile(handler llu.Handler, fname string, src any) error {
|
||||
fset := token.NewFileSet()
|
||||
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments)
|
||||
if err != nil {
|
||||
return llu.LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Go parser",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||
|
||||
switch n2 := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(n2.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(n2.Args) <= int(argNum) {
|
||||
argc := len(n2.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
|
||||
case *ast.CompositeLit:
|
||||
if strings.HasSuffix(fname, "models/unit/unit.go") {
|
||||
lluUnit.HandleCompositeUnit(handler, fset, n2)
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey")
|
||||
if matchInsPrefix != nil {
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.HandleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fname, "services/migrations/migrate.go") {
|
||||
lluMigrate.HandleMessengerInFunc(handler, fset, n2)
|
||||
}
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
switch n2.Tok {
|
||||
case token.CONST, token.VAR:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
for _, spec := range n2.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
// modules/web/middleware/binding.go:Validate uses the convention that structs
|
||||
// entries can have tags.
|
||||
// In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't.
|
||||
// Problem: we don't know which structs are forms, actually.
|
||||
|
||||
for _, spec := range n2.Specs {
|
||||
tspec := spec.(*ast.TypeSpec)
|
||||
structNode, ok := tspec.Type.(*ast.StructType)
|
||||
if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") ||
|
||||
(tspec.Doc != nil &&
|
||||
slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool {
|
||||
return c.Text == "// swagger:model"
|
||||
}))) {
|
||||
continue
|
||||
}
|
||||
for _, field := range structNode.Fields.List {
|
||||
if field.Names == nil {
|
||||
continue
|
||||
}
|
||||
if len(field.Names) != 1 {
|
||||
handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names")
|
||||
continue
|
||||
}
|
||||
msgidPos := field.Names[0].NamePos
|
||||
msgid := "form." + field.Names[0].Name
|
||||
if field.Tag != nil && field.Tag.Kind == token.STRING {
|
||||
rawTag, err := strconv.Unquote(field.Tag.Value)
|
||||
if err != nil {
|
||||
handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered")
|
||||
continue
|
||||
}
|
||||
tag := reflect.StructTag(rawTag)
|
||||
if tag.Get("form") == "-" {
|
||||
continue
|
||||
}
|
||||
tmp := tag.Get("locale")
|
||||
if len(tmp) != 0 {
|
||||
msgidPos = field.Tag.ValuePos
|
||||
msgid = tmp
|
||||
}
|
||||
}
|
||||
handler.OnMsgid(fset, msgidPos, msgid, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
|
@ -16,6 +16,7 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/translation/localeiter"
|
||||
)
|
||||
|
@ -23,27 +24,6 @@ import (
|
|||
// this works by first gathering all valid source string IDs from `en-US` reference files
|
||||
// and then checking if all used source strings are actually defined
|
||||
|
||||
type LocatedError struct {
|
||||
Location string
|
||||
Kind string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e LocatedError) Error() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(e.Location)
|
||||
sb.WriteString(":\t")
|
||||
if e.Kind != "" {
|
||||
sb.WriteString(e.Kind)
|
||||
sb.WriteString(": ")
|
||||
}
|
||||
sb.WriteString("ERROR: ")
|
||||
sb.WriteString(e.Err.Error())
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func InitLocaleTrFunctions() map[string][]uint {
|
||||
ret := make(map[string][]uint)
|
||||
|
||||
|
@ -58,14 +38,6 @@ func InitLocaleTrFunctions() map[string][]uint {
|
|||
return ret
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string)
|
||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||
LocaleTrFunctions map[string][]uint
|
||||
}
|
||||
|
||||
type StringTrie interface {
|
||||
Matches(key []string) bool
|
||||
}
|
||||
|
@ -113,7 +85,7 @@ func (m StringTrieMap) Insert(key []string) {
|
|||
func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], allowedMaskedPrefixes StringTrieMap, chkMsgid func(msgid string) bool) error {
|
||||
file, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return LocatedError{
|
||||
return llu.LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Open",
|
||||
Err: err,
|
||||
|
@ -133,7 +105,7 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
|||
allowedMaskedPrefixes.Insert(strings.Split(linePrefix, "."))
|
||||
} else {
|
||||
if !chkMsgid(line) {
|
||||
return LocatedError{
|
||||
return llu.LocatedError{
|
||||
Location: fmt.Sprintf("%s: line %d", fname, lno),
|
||||
Kind: "undefined msgid",
|
||||
Err: errors.New(line),
|
||||
|
@ -143,7 +115,7 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
|||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return LocatedError{
|
||||
return llu.LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Scanner",
|
||||
Err: err,
|
||||
|
@ -152,15 +124,6 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
|||
return nil
|
||||
}
|
||||
|
||||
// Truncating a message id prefix to the last dot
|
||||
func PrepareMsgidPrefix(s string) (string, bool) {
|
||||
index := strings.LastIndexByte(s, 0x2e)
|
||||
if index == -1 {
|
||||
return "", true
|
||||
}
|
||||
return s[:index], index != len(s)-1
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
outp := flag.CommandLine.Output()
|
||||
fmt.Fprintf(outp, "Usage of %s:\n", os.Args[0])
|
||||
|
@ -210,6 +173,7 @@ func Usage() {
|
|||
func main() {
|
||||
allowMissingMsgids := false
|
||||
allowUnusedMsgids := false
|
||||
allowWeakMissingMsgids := true
|
||||
usedMsgids := make(container.Set[string])
|
||||
allowedMaskedPrefixes := make(StringTrieMap)
|
||||
|
||||
|
@ -228,6 +192,12 @@ func main() {
|
|||
false,
|
||||
"don't return an error code if missing message IDs are found",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&allowWeakMissingMsgids,
|
||||
"allow-weak-missing-msgids",
|
||||
true,
|
||||
"Don't return an error code if missing 'weak' (e.g. \"form.$msgid\") message IDs are found",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&allowUnusedMsgids,
|
||||
"allow-unused-msgids",
|
||||
|
@ -289,7 +259,7 @@ func main() {
|
|||
os.Exit(3)
|
||||
}
|
||||
|
||||
handler := Handler{
|
||||
handler := llu.Handler{
|
||||
OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) {
|
||||
msgidPrefixSplit := strings.Split(msgidPrefix, ".")
|
||||
if !truncated {
|
||||
|
@ -299,8 +269,11 @@ func main() {
|
|||
fmt.Printf("%s:\tmissing msgid prefix: %s\n", fset.Position(pos).String(), msgidPrefix)
|
||||
}
|
||||
},
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) {
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||
if !msgids.Contains(msgid) {
|
||||
if weak && allowWeakMissingMsgids {
|
||||
return
|
||||
}
|
||||
gotAnyMsgidError = true
|
||||
fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid)
|
||||
} else {
|
||||
|
@ -314,7 +287,7 @@ func main() {
|
|||
OnWarning: func(fset *token.FileSet, pos token.Pos, msg string) {
|
||||
fmt.Printf("%s:\tWARNING: %s\n", fset.Position(pos).String(), msg)
|
||||
},
|
||||
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||
LocaleTrFunctions: llu.InitLocaleTrFunctions(),
|
||||
}
|
||||
|
||||
if err := filepath.WalkDir(".", func(fpath string, d fs.DirEntry, err error) error {
|
||||
|
@ -332,7 +305,7 @@ func main() {
|
|||
} else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" {
|
||||
// skip false positives
|
||||
} else if strings.HasSuffix(name, ".go") {
|
||||
onError(handler.HandleGoFile(fpath, nil))
|
||||
onError(HandleGoFile(handler, fpath, nil))
|
||||
} else if strings.HasSuffix(name, ".tmpl") {
|
||||
if strings.HasPrefix(fpath, "tests") && strings.HasSuffix(name, ".ini.tmpl") {
|
||||
// skip false positives
|
|
@ -7,24 +7,26 @@ import (
|
|||
"go/token"
|
||||
"testing"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func buildHandler(ret *[]string) Handler {
|
||||
return Handler{
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) {
|
||||
func buildHandler(ret *[]string) llu.Handler {
|
||||
return llu.Handler{
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||
*ret = append(*ret, msgid)
|
||||
},
|
||||
OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {},
|
||||
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||
LocaleTrFunctions: llu.InitLocaleTrFunctions(),
|
||||
}
|
||||
}
|
||||
|
||||
func HandleGoFileWrapped(t *testing.T, fname, src string) []string {
|
||||
var ret []string
|
||||
handler := buildHandler(&ret)
|
||||
require.NoError(t, handler.HandleGoFile(fname, src))
|
||||
require.NoError(t, HandleGoFile(handler, fname, src))
|
||||
return ret
|
||||
}
|
||||
|
|
@ -2,18 +2,17 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package main
|
||||
package lintLocaleUsage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
goParser "go/parser"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (handler Handler) handleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) {
|
||||
func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) {
|
||||
if argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
|
@ -29,14 +28,14 @@ func (handler Handler) handleGoTrBasicLit(fset *token.FileSet, argLit *ast.Basic
|
|||
}
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
||||
} else {
|
||||
handler.OnMsgid(fset, argLit.ValuePos, arg)
|
||||
handler.OnMsgid(fset, argLit.ValuePos, arg, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (handler Handler) handleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
||||
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.handleGoTrBasicLit(fset, argLit, prefix)
|
||||
handler.HandleGoTrBasicLit(fset, argLit, prefix)
|
||||
} else if argBinExpr, ok := n.(*ast.BinaryExpr); ok {
|
||||
if argBinExpr.Op != token.ADD {
|
||||
// pass
|
||||
|
@ -57,7 +56,7 @@ func (handler Handler) handleGoTrArgument(fset *token.FileSet, n ast.Expr, prefi
|
|||
}
|
||||
}
|
||||
|
||||
func (handler Handler) handleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string {
|
||||
func (handler Handler) HandleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string {
|
||||
if cg == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -87,131 +86,3 @@ func (handler Handler) handleGoCommentGroup(fset *token.FileSet, cg *ast.Comment
|
|||
return &matchInsPrefix
|
||||
}
|
||||
}
|
||||
|
||||
// the `Handle*File` functions follow the following calling convention:
|
||||
// * `fname` is the name of the input file
|
||||
// * `src` is either `nil` (then the function invokes `ReadFile` to read the file)
|
||||
// or the contents of the file as {`[]byte`, or a `string`}
|
||||
|
||||
func (handler Handler) HandleGoFile(fname string, src any) error {
|
||||
fset := token.NewFileSet()
|
||||
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments)
|
||||
if err != nil {
|
||||
return LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Go parser",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||
|
||||
switch n2 := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(n2.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(n2.Args) <= int(argNum) {
|
||||
argc := len(n2.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.handleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
case *ast.CompositeLit:
|
||||
ident, ok := n2.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// special case: models/unit/unit.go
|
||||
if strings.HasSuffix(fname, "unit.go") && ident.Name == "Unit" {
|
||||
if len(n2.Elts) != 6 {
|
||||
handler.OnWarning(fset, n2.Pos(), "unexpected initialization of 'Unit' (unexpected number of arguments)")
|
||||
}
|
||||
// NameKey has index 2
|
||||
// invoked like '{{ctx.Locale.Tr $unit.NameKey}}'
|
||||
nameKey, ok := n2.Elts[2].(*ast.BasicLit)
|
||||
if !ok || nameKey.Kind != token.STRING {
|
||||
handler.OnWarning(fset, n2.Elts[2].Pos(), "unexpected initialization of 'Unit' (expected string literal as NameKey)")
|
||||
return true
|
||||
}
|
||||
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(nameKey.Value)
|
||||
if err == nil {
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, nameKey.ValuePos, arg)
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
matchInsPrefix := handler.handleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.handleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
if !(n2.Tok == token.CONST || n2.Tok == token.VAR) {
|
||||
return true
|
||||
}
|
||||
matchInsPrefix := handler.handleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
for _, spec := range n2.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.handleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package main
|
||||
package lintLocaleUsage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -48,18 +48,29 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
|
|||
}
|
||||
|
||||
funcname := ""
|
||||
if nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode); ok {
|
||||
switch nodeCommand.Args[0].Type() {
|
||||
case tmplParser.NodeChain:
|
||||
nodeChain := nodeCommand.Args[0].(*tmplParser.ChainNode)
|
||||
if nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode); ok {
|
||||
if nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" {
|
||||
return
|
||||
}
|
||||
funcname = nodeChain.Field[1]
|
||||
}
|
||||
} else if nodeField, ok := nodeCommand.Args[0].(*tmplParser.FieldNode); ok {
|
||||
|
||||
case tmplParser.NodeField:
|
||||
nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode)
|
||||
if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") {
|
||||
return
|
||||
}
|
||||
funcname = nodeField.Ident[1]
|
||||
|
||||
case tmplParser.NodeVariable:
|
||||
nodeVar := nodeCommand.Args[0].(*tmplParser.VariableNode)
|
||||
if len(nodeVar.Ident) != 3 || !(nodeVar.Ident[0] == "$" && nodeVar.Ident[1] == "locale") {
|
||||
return
|
||||
}
|
||||
funcname = nodeVar.Ident[2]
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
@ -93,7 +104,7 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.
|
|||
case tmplParser.NodeString:
|
||||
nodeString := node.(*tmplParser.StringNode)
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, pos, nodeString.Text)
|
||||
handler.OnMsgid(fset, pos, nodeString.Text, false)
|
||||
|
||||
case tmplParser.NodePipe:
|
||||
nodePipe := node.(*tmplParser.PipeNode)
|
||||
|
@ -132,7 +143,7 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.
|
|||
|
||||
if len(nodeCommand.Args) == 2 {
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, stringPos, msgidPrefix)
|
||||
handler.OnMsgid(fset, stringPos, msgidPrefix, false)
|
||||
} else {
|
||||
if nodeIdent.Ident == "printf" {
|
||||
parts := strings.SplitN(msgidPrefix, "%", 2)
|
||||
|
|
62
build/lint-locale-usage/handler.go
Normal file
62
build/lint-locale-usage/handler.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package lintLocaleUsage
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LocatedError struct {
|
||||
Location string
|
||||
Kind string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e LocatedError) Error() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(e.Location)
|
||||
sb.WriteString(":\t")
|
||||
if e.Kind != "" {
|
||||
sb.WriteString(e.Kind)
|
||||
sb.WriteString(": ")
|
||||
}
|
||||
sb.WriteString("ERROR: ")
|
||||
sb.WriteString(e.Err.Error())
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func InitLocaleTrFunctions() map[string][]uint {
|
||||
ret := make(map[string][]uint)
|
||||
|
||||
f0 := []uint{0}
|
||||
ret["Tr"] = f0
|
||||
ret["TrString"] = f0
|
||||
ret["TrHTML"] = f0
|
||||
|
||||
ret["TrPluralString"] = []uint{1}
|
||||
ret["TrN"] = []uint{1, 2}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string, weak bool)
|
||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||
LocaleTrFunctions map[string][]uint
|
||||
}
|
||||
|
||||
// Truncating a message id prefix to the last dot
|
||||
func PrepareMsgidPrefix(s string) (string, bool) {
|
||||
index := strings.LastIndexByte(s, 0x2e)
|
||||
if index == -1 {
|
||||
return "", true
|
||||
}
|
||||
return s[:index], index != len(s)-1
|
||||
}
|
|
@ -28,6 +28,7 @@ func subcmdActionsGenRunnerToken() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "generate-runner-token",
|
||||
Usage: "Generate a new token for a runner to use to register with the server",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateActionsRunnerToken,
|
||||
Aliases: []string{"grt"},
|
||||
Flags: []cli.Flag{
|
||||
|
|
|
@ -37,6 +37,7 @@ func subcmdRepoSyncReleases() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "repo-sync-releases",
|
||||
Usage: "Synchronize repository releases with tags",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRepoSyncReleases,
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +76,7 @@ func subcmdSendMail() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "sendmail",
|
||||
Usage: "Send a message to all users",
|
||||
Before: noDanglingArgs,
|
||||
Action: runSendMail,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -103,7 +105,7 @@ func idFlag() *cli.Int64Flag {
|
|||
}
|
||||
}
|
||||
|
||||
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||
func runRepoSyncReleases(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ func microcmdAuthDelete() *cli.Command {
|
|||
Name: "delete",
|
||||
Usage: "Delete specific auth source",
|
||||
Flags: []cli.Flag{idFlag()},
|
||||
Before: noDanglingArgs,
|
||||
Action: runDeleteAuth,
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +40,7 @@ func microcmdAuthList() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List auth sources",
|
||||
Before: noDanglingArgs,
|
||||
Action: runListAuth,
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
|
|
|
@ -133,8 +133,9 @@ func ldapSimpleAuthCLIFlags() []cli.Flag {
|
|||
|
||||
func microcmdAuthAddLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-ldap",
|
||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||
Name: "add-ldap",
|
||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().addLdapBindDn(ctx, cli)
|
||||
},
|
||||
|
@ -144,8 +145,9 @@ func microcmdAuthAddLdapBindDn() *cli.Command {
|
|||
|
||||
func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-ldap",
|
||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||
Name: "update-ldap",
|
||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().updateLdapBindDn(ctx, cli)
|
||||
},
|
||||
|
@ -155,8 +157,9 @@ func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
|||
|
||||
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-ldap-simple",
|
||||
Usage: "Add new LDAP (simple auth) authentication source",
|
||||
Name: "add-ldap-simple",
|
||||
Usage: "Add new LDAP (simple auth) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().addLdapSimpleAuth(ctx, cli)
|
||||
},
|
||||
|
@ -166,8 +169,9 @@ func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
|||
|
||||
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-ldap-simple",
|
||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||
Name: "update-ldap-simple",
|
||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().updateLdapSimpleAuth(ctx, cli)
|
||||
},
|
||||
|
|
|
@ -136,6 +136,7 @@ func microcmdAuthAddOauth() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "add-oauth",
|
||||
Usage: "Add new Oauth authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().addOauth,
|
||||
Flags: oauthCLIFlags(),
|
||||
}
|
||||
|
@ -145,6 +146,7 @@ func microcmdAuthUpdateOauth() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "update-oauth",
|
||||
Usage: "Update existing Oauth authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().updateOauth,
|
||||
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...),
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ func microcmdAuthAddSMTP() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "add-smtp",
|
||||
Usage: "Add new SMTP authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddSMTP,
|
||||
Flags: smtpCLIFlags(),
|
||||
}
|
||||
|
@ -87,6 +88,7 @@ func microcmdAuthUpdateSMTP() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "update-smtp",
|
||||
Usage: "Update existing SMTP authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: runUpdateSMTP,
|
||||
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...),
|
||||
}
|
||||
|
|
|
@ -17,17 +17,19 @@ var (
|
|||
microcmdRegenHooks = &cli.Command{
|
||||
Name: "hooks",
|
||||
Usage: "Regenerate git-hooks",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateHooks,
|
||||
}
|
||||
|
||||
microcmdRegenKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "Regenerate authorized_keys file",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateKeys,
|
||||
}
|
||||
)
|
||||
|
||||
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
||||
func runRegenerateHooks(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
@ -37,7 +39,7 @@ func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
|||
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
||||
}
|
||||
|
||||
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
|
||||
func runRegenerateKeys(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ func microcmdUserChangePassword() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Before: noDanglingArgs,
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -23,6 +23,7 @@ func microcmdUserCreate() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a new user in database",
|
||||
Before: noDanglingArgs,
|
||||
Action: runCreateUser,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -40,6 +40,7 @@ func microcmdUserDelete() *cli.Command {
|
|||
Usage: "Purge user, all their repositories, organizations and comments",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runDeleteUser,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ func microcmdUserGenerateAccessToken() *cli.Command {
|
|||
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateAccessToken,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ func microcmdUserList() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List users",
|
||||
Before: noDanglingArgs,
|
||||
Action: runListUsers,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
|
@ -17,6 +17,7 @@ func microcmdUserResetMFA() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "reset-mfa",
|
||||
Usage: "Remove all two-factor authentication configurations for a user",
|
||||
Before: noDanglingArgs,
|
||||
Action: runResetMFA,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -31,6 +31,7 @@ func cmdCert() *cli.Command {
|
|||
Usage: "Generate self-signed certificate",
|
||||
Description: `Generate a self-signed X.509 certificate for a TLS server.
|
||||
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||
Before: noDanglingArgs,
|
||||
Action: runCert,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
29
cmd/cmd.go
29
cmd/cmd.go
|
@ -12,6 +12,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
|
@ -40,6 +41,19 @@ func argsSet(c *cli.Command, args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// When a CLI command is intended to be used only with flags and no other arbitrary args, noDanglingArgs will validate
|
||||
// the end-user's usage.
|
||||
func noDanglingArgs(ctx context.Context, c *cli.Command) (context.Context, error) {
|
||||
if c.Args().Len() != 0 {
|
||||
args := c.Args().Slice()
|
||||
if slices.Contains(args, "false") {
|
||||
println("Hint: boolean false must be specified as a single arg, eg. '--restricted=false', not '--restricted false'")
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected arguments: %s", strings.Join(c.Args().Slice(), ", "))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// confirm waits for user input which confirms an action
|
||||
func confirm() (bool, error) {
|
||||
var response string
|
||||
|
@ -135,3 +149,18 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context,
|
|||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
||||
func multipleBefore(beforeFuncs ...cli.BeforeFunc) cli.BeforeFunc {
|
||||
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
|
||||
for _, beforeFunc := range beforeFuncs {
|
||||
bctx, err := beforeFunc(ctx, cli)
|
||||
if err != nil {
|
||||
return bctx, err
|
||||
}
|
||||
if bctx != nil {
|
||||
ctx = bctx
|
||||
}
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ package cmd
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
golog "log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -13,13 +15,17 @@ import (
|
|||
"text/tabwriter"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/migrations"
|
||||
migrate_base "forgejo.org/models/migrations/base"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
migrate_base "forgejo.org/models/gitea_migrations/base"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/storage"
|
||||
"forgejo.org/services/doctor"
|
||||
|
||||
exif_terminator "code.superseriousbusiness.org/exif-terminator"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -34,6 +40,7 @@ func cmdDoctor() *cli.Command {
|
|||
cmdDoctorCheck(),
|
||||
cmdRecreateTable(),
|
||||
cmdDoctorConvert(),
|
||||
cmdAvatarStripExif(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +50,7 @@ func cmdDoctorCheck() *cli.Command {
|
|||
Name: "check",
|
||||
Usage: "Diagnose and optionally fix problems",
|
||||
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDoctorCheck,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
@ -98,6 +106,15 @@ You should back-up your database before doing this and ensure that your database
|
|||
}
|
||||
}
|
||||
|
||||
func cmdAvatarStripExif() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "avatar-strip-exif",
|
||||
Usage: "Strip EXIF metadata from all images in the avatar storage",
|
||||
Before: noDanglingArgs,
|
||||
Action: runAvatarStripExif,
|
||||
}
|
||||
}
|
||||
|
||||
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
||||
stdCtx, cancel := installSignals(stdCtx)
|
||||
defer cancel()
|
||||
|
@ -142,7 +159,7 @@ func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := migrations.EnsureUpToDate(engine); err != nil {
|
||||
if err := gitea_migrations.EnsureUpToDate(engine); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -230,3 +247,78 @@ func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
|
|||
}
|
||||
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
|
||||
}
|
||||
|
||||
func runAvatarStripExif(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := storage.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type HasCustomAvatarRelativePath interface {
|
||||
CustomAvatarRelativePath() string
|
||||
}
|
||||
|
||||
doExifStrip := func(obj HasCustomAvatarRelativePath, name string, target_storage storage.ObjectStorage) error {
|
||||
if obj.CustomAvatarRelativePath() == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Stripping avatar for %s...", name)
|
||||
|
||||
avatarFile, err := target_storage.Open(obj.CustomAvatarRelativePath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage.Avatars.Open: %w", err)
|
||||
}
|
||||
_, imgType, err := image.DecodeConfig(avatarFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("image.DecodeConfig: %w", err)
|
||||
}
|
||||
|
||||
// reset io.Reader for exif termination scan
|
||||
_, err = avatarFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("avatarFile.Seek: %w", err)
|
||||
}
|
||||
|
||||
cleanedData, err := exif_terminator.Terminate(avatarFile, imgType)
|
||||
if err != nil && strings.Contains(err.Error(), "cannot be processed") {
|
||||
// expected error for an image type that isn't supported by exif_terminator
|
||||
log.Info("... image type %s is not supported by exif_terminator, skipping.", imgType)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error cleaning exif data: %w", err)
|
||||
}
|
||||
|
||||
if err := storage.SaveFrom(target_storage, obj.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
_, err := io.Copy(w, cleanedData)
|
||||
return err
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Failed to create dir %s: %w", obj.CustomAvatarRelativePath(), err)
|
||||
}
|
||||
|
||||
log.Info("... completed %s.", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error {
|
||||
return doExifStrip(user, fmt.Sprintf("user %s", user.Name), storage.Avatars)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error {
|
||||
return doExifStrip(repo, fmt.Sprintf("repo %s", repo.Name), storage.RepoAvatars)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ func cmdDoctorConvert() *cli.Command {
|
|||
Name: "convert",
|
||||
Usage: "Convert the database",
|
||||
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDoctorConvert,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,6 +164,7 @@ func cmdDump() *cli.Command {
|
|||
Usage: "Dump Forgejo files and database",
|
||||
Description: `Dump compresses all related files and database into zip file.
|
||||
It can be used for backup and capture Forgejo server image to send to maintainer`,
|
||||
Before: noDanglingArgs,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -28,6 +28,7 @@ func cmdDumpRepository() *cli.Command {
|
|||
Name: "dump-repo",
|
||||
Usage: "Dump the repository from git/github/gitea/gitlab",
|
||||
Description: "This is a command for dumping the repository data.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDumpRepository,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -42,6 +42,7 @@ func microcmdGenerateInternalToken() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "INTERNAL_TOKEN",
|
||||
Usage: "Generate a new INTERNAL_TOKEN",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateInternalToken,
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +52,7 @@ func microcmdGenerateLfsJwtSecret() *cli.Command {
|
|||
Name: "JWT_SECRET",
|
||||
Aliases: []string{"LFS_JWT_SECRET"},
|
||||
Usage: "Generate a new JWT_SECRET",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateLfsJwtSecret,
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +61,7 @@ func microcmdGenerateSecretKey() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "SECRET_KEY",
|
||||
Usage: "Generate a new SECRET_KEY",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateSecretKey,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func cmdKeys() *cli.Command {
|
|||
Name: "keys",
|
||||
Usage: "(internal) Should only be called by SSH server",
|
||||
Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.FATAL)),
|
||||
Action: runKeys,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -39,6 +39,7 @@ func subcmdShutdown() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runShutdown,
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +53,7 @@ func subcmdRestart() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runRestart,
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +67,7 @@ func subcmdReloadTemplates() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runReloadTemplates,
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +76,7 @@ func subcmdFlushQueues() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "flush-queues",
|
||||
Usage: "Flush queues in the running process",
|
||||
Before: noDanglingArgs,
|
||||
Action: runFlushQueues,
|
||||
Flags: []cli.Flag{
|
||||
&cli.DurationFlag{
|
||||
|
@ -95,6 +99,7 @@ func subCmdProcesses() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "processes",
|
||||
Usage: "Display running processes within the current process",
|
||||
Before: noDanglingArgs,
|
||||
Action: runProcesses,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
|
@ -77,6 +77,7 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runPauseLogging,
|
||||
}, {
|
||||
Name: "resume",
|
||||
|
@ -86,6 +87,7 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runResumeLogging,
|
||||
}, {
|
||||
Name: "release-and-reopen",
|
||||
|
@ -95,6 +97,7 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runReleaseReopenLogging,
|
||||
}, {
|
||||
Name: "remove",
|
||||
|
@ -156,6 +159,7 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Compression level to use",
|
||||
},
|
||||
}...),
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddFileLogger,
|
||||
}, {
|
||||
Name: "conn",
|
||||
|
@ -182,6 +186,7 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Host address and port to connect to (defaults to :7020)",
|
||||
},
|
||||
}...),
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddConnLogger,
|
||||
},
|
||||
},
|
||||
|
@ -197,6 +202,7 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Switch off SQL logging",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runSetLogSQL,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"context"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/migrations"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
|
@ -20,6 +20,7 @@ func cmdMigrate() *cli.Command {
|
|||
Name: "migrate",
|
||||
Usage: "Migrate the database",
|
||||
Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runMigrate,
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ func runMigrate(stdCtx context.Context, ctx *cli.Command) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return migrations.Migrate(masterEngine)
|
||||
return gitea_migrations.Migrate(masterEngine)
|
||||
}); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
actions_model "forgejo.org/models/actions"
|
||||
"forgejo.org/models/db"
|
||||
git_model "forgejo.org/models/git"
|
||||
"forgejo.org/models/migrations"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
packages_model "forgejo.org/models/packages"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
|
@ -32,6 +32,7 @@ func cmdMigrateStorage() *cli.Command {
|
|||
Name: "migrate-storage",
|
||||
Usage: "Migrate the storage",
|
||||
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
||||
Before: noDanglingArgs,
|
||||
Action: runMigrateStorage,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -199,7 +200,7 @@ func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error {
|
|||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
|
||||
if err := db.InitEngineWithMigration(context.Background(), func(e db.Engine) error {
|
||||
return migrations.Migrate(e.(*xorm.Engine))
|
||||
return gitea_migrations.Migrate(e.(*xorm.Engine))
|
||||
}); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
|
|
|
@ -19,6 +19,7 @@ func cmdRestoreRepository() *cli.Command {
|
|||
Name: "restore-repo",
|
||||
Usage: "Restore the repository from disk",
|
||||
Description: "This is a command for restoring the repository data.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRestoreRepository,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -39,7 +39,7 @@ func cmdWeb() *cli.Command {
|
|||
Usage: "Start the Forgejo web server",
|
||||
Description: `The Forgejo web server is the only thing you need to run,
|
||||
and it takes care of all the other things for you`,
|
||||
Before: PrepareConsoleLoggerLevel(log.INFO),
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.INFO)),
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
@ -8,8 +8,8 @@ PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: '
|
|||
# Those must be explicitly required and are excluded from the full list of packages because they
|
||||
# would interfere with the testing fixtures.
|
||||
#
|
||||
excluded+='forgejo.org/models/migrations|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/models/forgejo_migrations|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/models/gitea_migrations|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/models/forgejo_migrations_legacy|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/tests/integration/migration-test|' # must be run before database specific tests
|
||||
excluded+='forgejo.org/tests|' # only tests, no coverage to get there
|
||||
excluded+='forgejo.org/tests/e2e|' # JavaScript is not in scope here and if it adds coverage it should not be counted
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; These values are environment-dependent but form the basis of a lot of values. They will be
|
||||
;; reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
|
||||
;; reported as part of the default configuration when running `forgejo help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
|
||||
;;
|
||||
;; - _`AppPath`_: This is the absolute path of the running gitea binary.
|
||||
;; - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy:
|
||||
;; - _`AppPath`_: This is the absolute path of the running Forgejo binary.
|
||||
;; - _`AppWorkPath`_: This refers to "working path" of the `forgejo` binary. It is determined by using the first set thing in the following hierarchy:
|
||||
;; - The "WORK_PATH" option in "app.ini" file
|
||||
;; - The `--work-path` flag passed to the binary
|
||||
;; - The environment variable `$GITEA_WORK_DIR`
|
||||
|
@ -54,7 +54,7 @@ APP_NAME = ; Forgejo: Beyond coding. We Forge.
|
|||
RUN_USER = ; git
|
||||
;;
|
||||
;; Application run mode, affects performance and debugging: "dev" or "prod", default is "prod"
|
||||
;; Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are treated as "prod" which is for production use.
|
||||
;; Mode "dev" makes Forgejo easier to develop and debug, values other than "dev" are treated as "prod" which is for production use.
|
||||
;RUN_MODE = prod
|
||||
;;
|
||||
;; The working directory, see the comment of AppWorkPath above
|
||||
|
@ -127,7 +127,7 @@ RUN_USER = ; git
|
|||
;; Permission for unix socket
|
||||
;UNIX_SOCKET_PERMISSION = 666
|
||||
;;
|
||||
;; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. In
|
||||
;; Local (DMZ) URL for Forgejo workers (such as SSH update) accessing web service. In
|
||||
;; most cases you do not need to change the default value. Alter it only if
|
||||
;; your SSH server node is not the same as HTTP node. For different protocol, the default
|
||||
;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
|
||||
|
@ -169,11 +169,11 @@ RUN_USER = ; git
|
|||
;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
|
||||
;SSH_ROOT_PATH =
|
||||
;;
|
||||
;; Gitea will create a authorized_keys file by default when it is not using the internal ssh server
|
||||
;; Forgejo will create a authorized_keys file by default when it is not using the internal ssh server
|
||||
;; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off.
|
||||
;SSH_CREATE_AUTHORIZED_KEYS_FILE = true
|
||||
;;
|
||||
;; Gitea will create a authorized_principals file by default when it is not using the internal ssh server
|
||||
;; Forgejo will create a authorized_principals file by default when it is not using the internal ssh server
|
||||
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
|
||||
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
|
||||
;;
|
||||
|
@ -198,7 +198,7 @@ RUN_USER = ; git
|
|||
;; default is the system temporary directory.
|
||||
;SSH_KEY_TEST_PATH =
|
||||
;;
|
||||
;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself.
|
||||
;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Forgejo does the parsing itself.
|
||||
;SSH_KEYGEN_PATH =
|
||||
;;
|
||||
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
||||
|
@ -220,9 +220,9 @@ RUN_USER = ; git
|
|||
;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
|
||||
;; For more information see "TrustedUserCAKeys" in the sshd config manpages.
|
||||
;SSH_TRUSTED_USER_CA_KEYS =
|
||||
;; Absolute path of the `TrustedUserCaKeys` file gitea will manage.
|
||||
;; Absolute path of the `TrustedUserCaKeys` file Forgejo will manage.
|
||||
;; Default this `RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem
|
||||
;; If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your
|
||||
;; If you're running your own ssh server and you want to use the Forgejo managed file you'll also need to modify your
|
||||
;; sshd_config to point to this file. The official docker image will automatically work without further configuration.
|
||||
;SSH_TRUSTED_USER_CA_KEYS_FILENAME =
|
||||
;;
|
||||
|
@ -277,7 +277,7 @@ RUN_USER = ; git
|
|||
;; Manual TLS settings: (Only applicable if ENABLE_ACME=false)
|
||||
;;
|
||||
;; Generate steps:
|
||||
;; $ ./gitea cert -ca=true -duration=8760h0m0s -host=myhost.example.com
|
||||
;; $ ./forgejo cert -ca=true -duration=8760h0m0s -host=myhost.example.com
|
||||
;;
|
||||
;; Or from a .pfx file exported from the Windows certificate store (do
|
||||
;; not forget to export the private key):
|
||||
|
@ -288,7 +288,7 @@ RUN_USER = ; git
|
|||
;KEY_FILE = https/key.pem
|
||||
;;
|
||||
;; Root directory containing templates and static files.
|
||||
;; default is the path where Gitea is executed
|
||||
;; default is the path where Forgejo is executed
|
||||
;STATIC_ROOT_PATH = ; Will default to the built-in value _`StaticRootPath`_
|
||||
;;
|
||||
;; Default path for App data
|
||||
|
@ -302,7 +302,7 @@ RUN_USER = ; git
|
|||
;; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)_<username>_<temporary id>
|
||||
;ENABLE_PPROF = false
|
||||
;;
|
||||
;; PPROF_DATA_PATH, use an absolute path when you start gitea as service
|
||||
;; PPROF_DATA_PATH, use an absolute path when you start Forgejo as service
|
||||
;PPROF_DATA_PATH = data/tmp/pprof ; Path is relative to _`AppWorkPath`_
|
||||
;;
|
||||
;; Landing page, can be "home", "explore", "organizations", "login", or any URL such as "/org/repo" or even "https://anotherwebsite.com"
|
||||
|
@ -374,7 +374,7 @@ DB_TYPE = sqlite3
|
|||
;USER = root
|
||||
;PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password.
|
||||
;SSL_MODE = false ; either "false" (default), "true", or "skip-verify"
|
||||
;CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
|
||||
;CHARSET_COLLATION = ; Empty as default, Forgejo will try to find a case-sensitive collation. Don't change it unless you clearly know what you need.
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
|
@ -440,7 +440,7 @@ SECRET_KEY =
|
|||
;; This key is VERY IMPORTANT. If you lose it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
|
||||
;SECRET_KEY_URI = file:/etc/gitea/secret_key
|
||||
;;
|
||||
;; Secret used to validate communication within Gitea binary.
|
||||
;; Secret used to validate communication within Forgejo binary.
|
||||
INTERNAL_TOKEN =
|
||||
;;
|
||||
;; Alternative location to specify internal token, instead of this file; you cannot specify both this and INTERNAL_TOKEN, and must pick one
|
||||
|
@ -474,9 +474,9 @@ INTERNAL_TOKEN =
|
|||
;;
|
||||
;; Set to false to allow users with git hook privileges to create custom git hooks.
|
||||
;; Custom git hooks can be used to perform arbitrary code execution on the host operating system.
|
||||
;; This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service.
|
||||
;; By modifying the Gitea database, users can gain Gitea administrator privileges.
|
||||
;; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user.
|
||||
;; This enables the users to access and modify this config file and the Forgejo database and interrupt the Forgejo service.
|
||||
;; By modifying the Forgejo database, users can gain Forgejo administrator privileges.
|
||||
;; It also enables them to access other resources available to the user on the operating system that is running the Forgejo instance and perform arbitrary actions in the name of the Forgejo OS user.
|
||||
;; WARNING: This maybe harmful to you website or your operating system.
|
||||
;; WARNING: Setting this to true does not change existing hooks in git repos; adjust it before if necessary.
|
||||
;DISABLE_GIT_HOOKS = true
|
||||
|
@ -484,7 +484,7 @@ INTERNAL_TOKEN =
|
|||
;; Set to true to disable webhooks feature.
|
||||
;DISABLE_WEBHOOKS = false
|
||||
;;
|
||||
;; Set to false to allow pushes to gitea repositories despite having an incomplete environment - NOT RECOMMENDED
|
||||
;; Set to false to allow pushes to Forgejo repositories despite having an incomplete environment - NOT RECOMMENDED
|
||||
;ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true
|
||||
;;
|
||||
;;Comma separated list of character classes required to pass minimum complexity.
|
||||
|
@ -545,7 +545,7 @@ ENABLED = true
|
|||
;; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
|
||||
;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem
|
||||
;;
|
||||
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://docs.gitea.io/en-us/command-line/#generate
|
||||
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://forgejo.org/docs/latest/admin/command-line/#generate-secret
|
||||
;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to HS256, HS384 or HS512.
|
||||
;JWT_SECRET =
|
||||
;;
|
||||
|
@ -670,7 +670,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; The path of git executable. If empty, Gitea searches through the PATH environment.
|
||||
;; The path of git executable. If empty, Forgejo searches through the PATH environment.
|
||||
;PATH =
|
||||
;;
|
||||
;; The HOME directory for Git
|
||||
|
@ -755,17 +755,17 @@ LEVEL = Info
|
|||
;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.)
|
||||
;REGISTER_MANUAL_CONFIRM = false
|
||||
;;
|
||||
;; List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported
|
||||
;; eg: gitea.io,example.com,*.mydomain.com
|
||||
;; List of domain names that are allowed to be used to register on a Forgejo instance, wildcard is supported
|
||||
;; eg: forgejo.org,example.com,*.mydomain.com
|
||||
;EMAIL_DOMAIN_ALLOWLIST =
|
||||
;;
|
||||
;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported
|
||||
;; Comma-separated list of domain names that are not allowed to be used to register on a Forgejo instance, wildcard is supported
|
||||
;EMAIL_DOMAIN_BLOCKLIST =
|
||||
;;
|
||||
;; Disallow registration, only allow admins to create accounts.
|
||||
;DISABLE_REGISTRATION = false
|
||||
;;
|
||||
;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
|
||||
;; Allow registration only using Forgejo itself, it works only when DISABLE_REGISTRATION is false
|
||||
;ALLOW_ONLY_INTERNAL_REGISTRATION = false
|
||||
;;
|
||||
;; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false
|
||||
|
@ -777,7 +777,7 @@ LEVEL = Info
|
|||
;; Mail notification
|
||||
;ENABLE_NOTIFY_MAIL = false
|
||||
;;
|
||||
;; This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password
|
||||
;; This setting enables Forgejo to be signed in with HTTP BASIC Authentication using the user's password
|
||||
;; If you set this to false you will not be able to access the tokens endpoints on the API with your password
|
||||
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
||||
;ENABLE_BASIC_AUTHENTICATION = true
|
||||
|
@ -1012,7 +1012,7 @@ LEVEL = Info
|
|||
;; Close issues as long as a commit on any branch marks it as fixed
|
||||
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
||||
;;
|
||||
;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
|
||||
;; Allow users to push local repositories to Forgejo and have them automatically created for a user or an org
|
||||
;ENABLE_PUSH_CREATE_USER = false
|
||||
;ENABLE_PUSH_CREATE_ORG = false
|
||||
;;
|
||||
|
@ -1076,7 +1076,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart)
|
||||
;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on Forgejo restart)
|
||||
;LOCAL_COPY_PATH = tmp/local-repo
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1088,7 +1088,7 @@ LEVEL = Info
|
|||
;; Whether repository file uploads are enabled. Defaults to `true`
|
||||
;ENABLED = true
|
||||
;;
|
||||
;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart)
|
||||
;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on Forgejo restart)
|
||||
;TEMP_PATH = data/tmp/uploads
|
||||
;;
|
||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
|
@ -1187,7 +1187,7 @@ LEVEL = Info
|
|||
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
|
||||
;DEFAULT_TRUST_MODEL = collaborator
|
||||
;;
|
||||
;; Determines when gitea should sign the initial commit when creating a repository
|
||||
;; Determines when Forgejo should sign the initial commit when creating a repository
|
||||
;; Either:
|
||||
;; - never
|
||||
;; - pubkey: only sign if the user has a pubkey
|
||||
|
@ -1301,7 +1301,7 @@ LEVEL = Info
|
|||
;; Whether the email of the user should be shown in the Explore Users page
|
||||
;SHOW_USER_EMAIL = true
|
||||
;;
|
||||
;; Set the default theme for the Gitea install
|
||||
;; Set the default theme for the Forgejo install
|
||||
;DEFAULT_THEME = forgejo-auto
|
||||
;;
|
||||
;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`.
|
||||
|
@ -1338,10 +1338,6 @@ LEVEL = Info
|
|||
;; Change the sort type of the explore pages.
|
||||
;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest".
|
||||
;EXPLORE_PAGING_DEFAULT_SORT = recentupdate
|
||||
;;
|
||||
;; The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`.
|
||||
;; `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago).
|
||||
;PREFERRED_TIMESTAMP_TENSE = mixed
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1710,10 +1706,10 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; NOTICE: this section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
|
||||
;; NOTICE: this section is for Forgejo 1.18 and later. If you are using Forgejo 1.17 or older,
|
||||
;; please refer to
|
||||
;; https://github.com/go-gitea/gitea/blob/release/v1.17/custom/conf/app.example.ini
|
||||
;; https://github.com/go-gitea/gitea/blob/release/v1.17/docs/content/doc/advanced/config-cheat-sheet.en-us.md
|
||||
;; https://codeberg.org/forgejo/forgejo/src/commit/8769df117d6cc2f4ab00d6e1d54ef4241d063f11/custom/conf/app.example.ini
|
||||
;; https://codeberg.org/forgejo/forgejo/src/commit/8769df117d6cc2f4ab00d6e1d54ef4241d063f11/docs/content/doc/advanced/config-cheat-sheet.en-us.md
|
||||
;;
|
||||
;ENABLED = false
|
||||
;;
|
||||
|
@ -1766,7 +1762,7 @@ LEVEL = Info
|
|||
;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address.
|
||||
;ENVELOPE_FROM =
|
||||
;;
|
||||
;; If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) <gitea@codeit.net>`,
|
||||
;; If Forgejo sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `John Doe (by AppName) <johndoe@example.com>`,
|
||||
;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`.
|
||||
;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }}
|
||||
;;
|
||||
|
@ -1927,7 +1923,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; How Gitea deals with missing repository avatars
|
||||
;; How Forgejo deals with missing repository avatars
|
||||
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
||||
;REPOSITORY_AVATAR_FALLBACK = none
|
||||
;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
|
||||
|
@ -2045,7 +2041,7 @@ LEVEL = Info
|
|||
;;
|
||||
;; Setting this to true will enable all cron tasks periodically with default settings.
|
||||
;ENABLED = false
|
||||
;; Setting this to true will run all enabled cron tasks when Gitea starts.
|
||||
;; Setting this to true will run all enabled cron tasks when Forgejo starts.
|
||||
;RUN_AT_START = false
|
||||
;;
|
||||
;; Note: ``SCHEDULE`` accept formats
|
||||
|
@ -2085,7 +2081,7 @@ LEVEL = Info
|
|||
;SCHEDULE = @every 10m
|
||||
;; Enable running Update mirrors task periodically.
|
||||
;ENABLED = true
|
||||
;; Run Update mirrors task when Gitea starts.
|
||||
;; Run Update mirrors task when Forgejo starts.
|
||||
;RUN_AT_START = false
|
||||
;; Notice if not success
|
||||
;NOTICE_ON_SUCCESS = false
|
||||
|
@ -2106,7 +2102,7 @@ LEVEL = Info
|
|||
;SCHEDULE = @midnight
|
||||
;; Enable running Repository health check task periodically.
|
||||
;ENABLED = true
|
||||
;; Run Repository health check task when Gitea starts.
|
||||
;; Run Repository health check task when Forgejo starts.
|
||||
;RUN_AT_START = false
|
||||
;; Notice if not success
|
||||
;NOTICE_ON_SUCCESS = false
|
||||
|
@ -2124,7 +2120,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Enable running check repository statistics task periodically.
|
||||
;ENABLED = true
|
||||
;; Run check repository statistics task when Gitea starts.
|
||||
;; Run check repository statistics task when Forgejo starts.
|
||||
;RUN_AT_START = true
|
||||
;; Notice if not success
|
||||
;NOTICE_ON_SUCCESS = false
|
||||
|
@ -2279,7 +2275,7 @@ LEVEL = Info
|
|||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Update the '.ssh/authorized_keys' file with Gitea SSH keys
|
||||
;; Update the '.ssh/authorized_keys' file with Forgejo SSH keys
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[cron.resync_all_sshkeys]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2352,7 +2348,7 @@ LEVEL = Info
|
|||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Check for new Gitea versions
|
||||
;; Check for new Forgejo versions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[cron.update_checker]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2453,7 +2449,7 @@ LEVEL = Info
|
|||
;[other]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Show version information about Gitea and Go in the footer
|
||||
;; Show version information about Forgejo and Go in the footer
|
||||
;SHOW_FOOTER_VERSION = true
|
||||
;; Show template execution time in the footer
|
||||
;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
|
||||
|
@ -2700,7 +2696,7 @@ LEVEL = Info
|
|||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; settings for Gitea's LFS client (eg: mirroring an upstream lfs endpoint)
|
||||
;; settings for Forgejo's LFS client (eg: mirroring an upstream lfs endpoint)
|
||||
;;
|
||||
;[lfs_client]
|
||||
;; Limit the number of pointers in each batch request to this number
|
||||
|
@ -2786,6 +2782,10 @@ LEVEL = Info
|
|||
;SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip]
|
||||
;; Limit on inputs for manual / workflow_dispatch triggers, default is 10
|
||||
;LIMIT_DISPATCH_INPUTS = 10
|
||||
;; Support queuing workflow jobs, by setting `concurrency.group` & `concurrency.cancel-in-progress: false`, can increase
|
||||
;; server and database workload due to more complex database queries and more frequent server task querying; this
|
||||
;; feature can be disabled to reduce performance impact
|
||||
;CONCURRENCY_GROUP_QUEUE_ENABLED = true
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -58,7 +58,14 @@ export default tseslint.config(
|
|||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'off', // TODO: enable this rule again
|
||||
'@typescript-eslint/no-unused-vars': [2, {
|
||||
args: 'all',
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
ignoreRestSiblings: false,
|
||||
}],
|
||||
|
||||
'@eslint-community/eslint-comments/disable-enable-pair': [2],
|
||||
'@eslint-community/eslint-comments/no-aggregating-enable': [2],
|
||||
|
@ -539,14 +546,7 @@ export default tseslint.config(
|
|||
'no-unused-labels': [2],
|
||||
'no-unused-private-class-members': [2],
|
||||
|
||||
'no-unused-vars': [2, {
|
||||
args: 'all',
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
ignoreRestSiblings: false,
|
||||
}],
|
||||
'no-unused-vars': [0],
|
||||
|
||||
'no-use-before-define': [2, {
|
||||
functions: false,
|
||||
|
@ -703,7 +703,6 @@ export default tseslint.config(
|
|||
'sonarjs/no-inverted-boolean-check': [2],
|
||||
'sonarjs/no-nested-switch': [0],
|
||||
'sonarjs/no-nested-template-literals': [0],
|
||||
'sonarjs/no-one-iteration-loop': [2],
|
||||
'sonarjs/no-redundant-boolean': [2],
|
||||
'sonarjs/no-redundant-jump': [2],
|
||||
'sonarjs/no-same-line-conditional': [2],
|
||||
|
@ -1083,6 +1082,7 @@ export default tseslint.config(
|
|||
'@vitest/no-standalone-expect': [0],
|
||||
'@vitest/no-test-prefixes': [0],
|
||||
'@vitest/no-test-return-statement': [0],
|
||||
'@vitest/prefer-called-exactly-once-with': [2],
|
||||
'@vitest/prefer-called-with': [0],
|
||||
'@vitest/prefer-comparison-matcher': [0],
|
||||
'@vitest/prefer-each': [0],
|
||||
|
@ -1090,6 +1090,7 @@ export default tseslint.config(
|
|||
'@vitest/prefer-expect-resolves': [0],
|
||||
'@vitest/prefer-hooks-in-order': [0],
|
||||
'@vitest/prefer-hooks-on-top': [2],
|
||||
'@vitest/prefer-import-in-mock': [0],
|
||||
'@vitest/prefer-lowercase-title': [0],
|
||||
'@vitest/prefer-mock-promise-shorthand': [0],
|
||||
'@vitest/prefer-snapshot-hint': [0],
|
||||
|
|
73
go.mod
73
go.mod
|
@ -2,23 +2,25 @@ module forgejo.org
|
|||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.7
|
||||
toolchain go1.25.3
|
||||
|
||||
require (
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.1
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0
|
||||
code.forgejo.org/forgejo/levelqueue v1.0.0
|
||||
code.forgejo.org/forgejo/reply v1.0.2
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.1
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.2
|
||||
code.forgejo.org/go-chi/binding v1.0.1
|
||||
code.forgejo.org/go-chi/cache v1.0.1
|
||||
code.forgejo.org/go-chi/captcha v1.0.2
|
||||
code.forgejo.org/go-chi/session v1.0.2
|
||||
code.gitea.io/actions-proto-go v0.4.0
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.18.1
|
||||
connectrpc.com/connect v1.19.1
|
||||
github.com/42wim/httpsig v1.2.3
|
||||
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
|
@ -34,6 +36,7 @@ require (
|
|||
github.com/djherbis/buffer v1.2.0
|
||||
github.com/djherbis/nio/v3 v3.0.1
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
|
@ -47,7 +50,7 @@ require (
|
|||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.9.2
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-openapi/spec v0.21.0
|
||||
github.com/go-openapi/spec v0.22.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/go-webauthn/webauthn v0.14.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
|
@ -56,7 +59,7 @@ require (
|
|||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/go-github/v64 v64.0.0
|
||||
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5
|
||||
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.2.0
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
|
@ -74,7 +77,7 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/meilisearch/meilisearch-go v0.34.0
|
||||
github.com/mholt/archives v0.1.4
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/minio-go/v7 v7.0.95
|
||||
github.com/msteinert/pam/v2 v2.1.0
|
||||
|
@ -96,17 +99,17 @@ require (
|
|||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2
|
||||
go.uber.org/mock v0.6.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/image v0.31.0
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/oauth2 v0.31.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/image v0.32.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/oauth2 v0.32.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/sys v0.36.0
|
||||
golang.org/x/text v0.29.0
|
||||
google.golang.org/protobuf v1.36.9
|
||||
golang.org/x/sys v0.37.0
|
||||
golang.org/x/text v0.30.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
|
@ -116,6 +119,7 @@ require (
|
|||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
|
@ -145,7 +149,7 @@ require (
|
|||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
|
@ -160,6 +164,10 @@ require (
|
|||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
|
@ -167,17 +175,26 @@ require (
|
|||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.16.2 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||
github.com/go-webauthn/x v0.1.25 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
|
@ -189,7 +206,7 @@ require (
|
|||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
|
@ -198,7 +215,7 @@ require (
|
|||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/markbates/going v1.0.3 // 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.17 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
|
@ -212,7 +229,7 @@ require (
|
|||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.1.1 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.0.7 // indirect
|
||||
|
@ -225,13 +242,13 @@ require (
|
|||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rhysd/actionlint v1.7.7 // indirect
|
||||
github.com/rhysd/actionlint v1.7.8 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
|
@ -243,12 +260,14 @@ require (
|
|||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
@ -260,4 +279,4 @@ replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-2024121
|
|||
|
||||
replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
||||
|
||||
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.1
|
||||
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.3
|
||||
|
|
183
go.sum
183
go.sum
|
@ -16,8 +16,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
|
|||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFsAE=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.1 h1:c0vE8XvqpbXuSv8gzttn96k5T2FQi0u9bYnux46qSAs=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.1/go.mod h1:1p2UKrqZiwxKneQF2DKrMnc403YIgR/lfcfvadZtmDs=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M=
|
||||
|
@ -28,8 +28,8 @@ code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/Uf
|
|||
code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.1 h1:CoSfxBOLKLMJZq5VhKd5ZIUc3tCf04iyFx926s+zaMA=
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.1/go.mod h1:9f0D2EG7uabL+cv71SWHKrGgo2vmLpvko0QLmtn3RDE=
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.2 h1:jM5YsNmScH11VJEwmvsTUiqGjAqtiUzBhQ65BIo8ZOs=
|
||||
code.forgejo.org/forgejo/runner/v11 v11.1.2/go.mod h1:9f0D2EG7uabL+cv71SWHKrGgo2vmLpvko0QLmtn3RDE=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
|
||||
|
@ -40,16 +40,22 @@ code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkA
|
|||
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
|
||||
code.forgejo.org/go-chi/session v1.0.2 h1:pG+AXre9L9VXJmTaADXkmeEPuRalhmBXyv6tG2Rvjcc=
|
||||
code.forgejo.org/go-chi/session v1.0.2/go.mod h1:HnEGyBny7WPzCiVLP2vzL5ssma+3gCSl/vLpuVNYrqc=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.1 h1:jU5CICzbkpqol6qTa4yctLuS0BI3D2mVCx7q6ogK/gE=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.1/go.mod h1:0q+miQUSpTlsgMw2blcO9a87GB5WyatiX3GuwBca31w=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.3 h1:7KW1+3cr2YIZCS85TM+imdsUe8OdNsfV3F8iP2t36rM=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.3/go.mod h1:5ouTxqMcalQUvlBpQynRpzu/44GwaMpkA1nU+encsDE=
|
||||
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
|
||||
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
|
||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0 h1:Hof0MCcsa+1fS17gf86fTTZ8AQnMY9h9kzcc+2C6mVg=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0/go.mod h1:9sutT1axa/kSdlPLlRFjCNKmyo/KNx8eX3XZvWBlAEY=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
|
||||
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
|
||||
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
|
||||
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
|
@ -143,8 +149,8 @@ github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFx
|
|||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
|
||||
|
@ -209,6 +215,28 @@ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn
|
|||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1 h1:/IE4iW7gvY7BablV1XY0unqhMv26EYpOquVMwoBo/wc=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1/go.mod h1:10HkA1Wz3h398cDP66L+Is9kKDmlqlIJGPv8pk4EWvc=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM=
|
||||
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c=
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs=
|
||||
|
@ -258,6 +286,11 @@ github.com/go-enry/go-enry/v2 v2.9.2 h1:giOQAtCgBX08kosrX818DCQJTCNtKwoPBGu0qb6n
|
|||
github.com/go-enry/go-enry/v2 v2.9.2/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
|
@ -274,14 +307,28 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
|||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
|
||||
github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
|
||||
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
|
||||
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
|
||||
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
|
||||
github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw=
|
||||
github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0=
|
||||
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
|
||||
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg=
|
||||
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
|
||||
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
|
@ -291,6 +338,8 @@ github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx
|
|||
github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k=
|
||||
github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88=
|
||||
github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
|
@ -308,6 +357,10 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v
|
|||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -360,8 +413,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
|
|||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
|
||||
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8 h1:ZI8gCoCjGzPsum4L21jHdQs8shFBIQih1TM9Rd/c+EQ=
|
||||
github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -387,8 +440,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
|
|||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
|
@ -404,6 +457,8 @@ github.com/inbucket/html2text v0.9.0 h1:ULJmVcBEMAcmLE+/rN815KG1Fx6+a4HhbUxiDiN+
|
|||
github.com/inbucket/html2text v0.9.0/go.mod h1:QDaumzl+/OzlSVbNohhmg+yAy5pKjUjzCKW2BMvztKE=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0 h1:Pe35MB96eZK5Q0XjlvPftOgWypQpd1gcbfJKAt7rsB8=
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0/go.mod h1:SOBXlCemjhiV2DvHhAKnJiWrtJGS/Ffuw4Iy7NjBTaI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
|
@ -453,8 +508,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
|
||||
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
|
@ -463,8 +518,8 @@ github.com/meilisearch/meilisearch-go v0.34.0 h1:P+Ohdx4/PCxXaoI5wNi0LMwPkuiNrF/
|
|||
github.com/meilisearch/meilisearch-go v0.34.0/go.mod h1:cUVJZ2zMqTvvwIMEEAdsWH+zrHsrLpAw6gm8Lt1MXK0=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/mholt/archives v0.1.4 h1:sU+/lLNgafUontWFv3AVwO8VUWye3rrtN6hgC2dU11c=
|
||||
github.com/mholt/archives v0.1.4/go.mod h1:I2ia+SQTtQHej9w1GZM/mz7qfdgQv+BHr3hEKqDcGuk=
|
||||
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
|
@ -498,8 +553,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
|
|||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
|
||||
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
|
||||
github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew=
|
||||
github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
|
@ -553,8 +608,8 @@ github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhi
|
|||
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rhysd/actionlint v1.7.7 h1:0KgkoNTrYY7vmOCs9BW2AHxLvvpoY9nEUzgBHiPUr0k=
|
||||
github.com/rhysd/actionlint v1.7.7/go.mod h1:AE6I6vJEkNaIfWqC2GNE5spIJNhxf8NCtLEKU4NnUXg=
|
||||
github.com/rhysd/actionlint v1.7.8 h1:3d+N9ourgAxVYG4z2IFxFIk/YiT6V+VnKASfXGwT60E=
|
||||
github.com/rhysd/actionlint v1.7.8/go.mod h1:3kiS6egcbXG+vQsJIhFxTz+UKaF1JprsE0SKrpCZKvU=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
|
@ -580,8 +635,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
|
|||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -632,8 +687,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
|||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc=
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2 h1:tfmUW8u+G/DGKOB/FDR0c06f0RVUAEe0ym8WpLoiHXI=
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2/go.mod h1:gJn5yLx9vYGXr73Yv0ueHWCVl+fL8iUOgJFxC7qV+iM=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
@ -655,6 +710,8 @@ go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
|||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -669,8 +726,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
|||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -683,8 +740,8 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y
|
|||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
|
||||
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
|
||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -706,8 +763,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -722,11 +779,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
|
@ -734,15 +796,15 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -780,12 +842,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -793,8 +858,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -804,8 +869,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -819,8 +884,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
|
@ -854,8 +919,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -899,8 +964,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -921,8 +986,10 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
|
|||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -933,14 +1000,14 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
||||
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
|
||||
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
|
|
@ -25,11 +25,23 @@ import (
|
|||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type ConcurrencyMode int
|
||||
|
||||
const (
|
||||
// Don't enforce concurrency control. Note that you won't find `UnlimitedConcurrency` implemented directly in the
|
||||
// code; setting it on an `ActionRun` prevents the other limiting behaviors.
|
||||
UnlimitedConcurrency ConcurrencyMode = iota
|
||||
// Queue behind other jobs with the same concurrency group
|
||||
QueueBehind
|
||||
// Cancel other jobs with the same concurrency group
|
||||
CancelInProgress
|
||||
)
|
||||
|
||||
// ActionRun represents a run of a workflow file
|
||||
type ActionRun struct {
|
||||
ID int64
|
||||
Title string
|
||||
RepoID int64 `xorm:"index unique(repo_index)"`
|
||||
RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
WorkflowID string `xorm:"index"` // the name of workflow file
|
||||
|
@ -56,6 +68,11 @@ type ActionRun struct {
|
|||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
NotifyEmail bool
|
||||
|
||||
ConcurrencyGroup string `xorm:"'concurrency_group' index(concurrency)"`
|
||||
ConcurrencyType ConcurrencyMode
|
||||
|
||||
PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -163,6 +180,24 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
|
|||
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
|
||||
}
|
||||
|
||||
func (run *ActionRun) SetConcurrencyGroup(concurrencyGroup string) {
|
||||
// Concurrency groups are case insensitive identifiers, implemented by collapsing case here. Unfortunately the
|
||||
// `ConcurrencyGroup` field can't be made a private field because xorm doesn't map those fields -- using
|
||||
// `SetConcurrencyGroup` is required for consistency but not enforced at compile-time.
|
||||
run.ConcurrencyGroup = strings.ToLower(concurrencyGroup)
|
||||
}
|
||||
|
||||
func (run *ActionRun) SetDefaultConcurrencyGroup() {
|
||||
// Before ConcurrencyGroups were supported, Forgejo would automatically cancel runs with matching git refs, workflow
|
||||
// IDs, and trigger events. For backwards compatibility we emulate that behavior:
|
||||
run.SetConcurrencyGroup(fmt.Sprintf(
|
||||
"%s_%s_%s__auto",
|
||||
run.Ref,
|
||||
run.WorkflowID,
|
||||
run.TriggerEvent,
|
||||
))
|
||||
}
|
||||
|
||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||
SetExpr("num_action_runs",
|
||||
|
@ -183,6 +218,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
|
|||
),
|
||||
),
|
||||
).
|
||||
Cols("num_action_runs", "num_closed_action_runs").
|
||||
Update(repo)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package actions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
|
@ -64,14 +65,15 @@ func (runs RunList) LoadRepos(ctx context.Context) error {
|
|||
|
||||
type FindRunOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
ConcurrencyGroup string
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) ToConds() builder.Cond {
|
||||
|
@ -100,6 +102,9 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
|||
if opts.TriggerEvent != "" {
|
||||
cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
|
||||
}
|
||||
if opts.ConcurrencyGroup != "" {
|
||||
cond = cond.And(builder.Eq{"concurrency_group": strings.ToLower(opts.ConcurrencyGroup)})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,84 @@ package actions
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRunBefore(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetConcurrencyGroup(t *testing.T) {
|
||||
run := ActionRun{}
|
||||
run.SetConcurrencyGroup("abc123")
|
||||
assert.Equal(t, "abc123", run.ConcurrencyGroup)
|
||||
run.SetConcurrencyGroup("ABC123") // case should collapse in SetConcurrencyGroup
|
||||
assert.Equal(t, "abc123", run.ConcurrencyGroup)
|
||||
}
|
||||
|
||||
func TestSetDefaultConcurrencyGroup(t *testing.T) {
|
||||
run := ActionRun{
|
||||
Ref: "refs/heads/main",
|
||||
WorkflowID: "testing",
|
||||
TriggerEvent: "pull_request",
|
||||
}
|
||||
run.SetDefaultConcurrencyGroup()
|
||||
assert.Equal(t, "refs/heads/main_testing_pull_request__auto", run.ConcurrencyGroup)
|
||||
run = ActionRun{
|
||||
Ref: "refs/heads/main",
|
||||
WorkflowID: "TESTING", // case should collapse in SetDefaultConcurrencyGroup
|
||||
TriggerEvent: "pull_request",
|
||||
}
|
||||
run.SetDefaultConcurrencyGroup()
|
||||
assert.Equal(t, "refs/heads/main_testing_pull_request__auto", run.ConcurrencyGroup)
|
||||
}
|
||||
|
||||
func TestUpdateRepoRunsNumbers(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
t.Run("Repo 1", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, 1, repo.NumActionRuns)
|
||||
assert.Equal(t, 1, repo.NumClosedActionRuns)
|
||||
})
|
||||
|
||||
t.Run("Repo 4", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
assert.Equal(t, 4, repo.NumActionRuns)
|
||||
assert.Equal(t, 4, repo.NumClosedActionRuns)
|
||||
})
|
||||
|
||||
t.Run("Repo 63", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63})
|
||||
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63})
|
||||
assert.Equal(t, 3, repo.NumActionRuns)
|
||||
assert.Equal(t, 2, repo.NumClosedActionRuns)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Columns specifc", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
repo.Name = "ishouldnotbeupdated"
|
||||
|
||||
require.NoError(t, updateRepoRunsNumbers(t.Context(), repo))
|
||||
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, "repo1", repo.Name)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -239,6 +239,88 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
|
|||
return nil, errNotExist
|
||||
}
|
||||
|
||||
func getConcurrencyCondition() builder.Cond {
|
||||
concurrencyCond := builder.NewCond()
|
||||
|
||||
// OK to pick if there's no concurrency_group on the run
|
||||
concurrencyCond = concurrencyCond.Or(builder.Eq{"concurrency_group": ""})
|
||||
concurrencyCond = concurrencyCond.Or(builder.IsNull{"concurrency_group"})
|
||||
|
||||
// OK to pick if it's not a "QueueBehind" concurrency type
|
||||
concurrencyCond = concurrencyCond.Or(builder.Neq{"concurrency_type": QueueBehind})
|
||||
|
||||
// subQuery ends up representing all the runs that would block a run from executing:
|
||||
subQuery := builder.Select("id").From("action_run", "inner_run").
|
||||
// A run can't block itself, so exclude it from this search
|
||||
Where(builder.Neq{"inner_run.id": builder.Expr("outer_run.id")}).
|
||||
// Blocking runs must be from the same repo & concurrency group
|
||||
And(builder.Eq{"inner_run.repo_id": builder.Expr("outer_run.repo_id")}).
|
||||
And(builder.Eq{"inner_run.concurrency_group": builder.Expr("outer_run.concurrency_group")}).
|
||||
And(
|
||||
// Ideally the logic here would be that a blocking run is "not done", and "younger", which allows each run
|
||||
// to be blocked on the previous runs in the concurrency group and therefore execute in order from oldest to
|
||||
// newest.
|
||||
//
|
||||
// But it's possible for runs to be required to run out-of-order -- for example, if a younger run has
|
||||
// already completed but then it is re-run. If we only used "not done" and "younger" as logic, then the
|
||||
// re-run would not be blocked, and therefore would violate the concurrency group's single-run goal.
|
||||
//
|
||||
// So we use two conditions to meet both needs:
|
||||
//
|
||||
// Blocking runs have a running status...
|
||||
builder.Eq{"inner_run.status": StatusRunning}.Or(
|
||||
// Blocking runs don't have a IsDone status & are younger than the outer_run
|
||||
builder.NotIn("inner_run.status", []Status{StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped}).
|
||||
And(builder.Lt{"inner_run.`index`": builder.Expr("outer_run.`index`")})))
|
||||
|
||||
// OK to pick if there are no blocking runs
|
||||
concurrencyCond = concurrencyCond.Or(builder.NotExists(subQuery))
|
||||
|
||||
return concurrencyCond
|
||||
}
|
||||
|
||||
// Returns all the available jobs that could be executed on `runner`, before label filtering is applied. Note that
|
||||
// only a single job can actually be run from this result for any given invocation, as multiple runs (in order) from any
|
||||
// single concurrency group could be returned.
|
||||
func GetAvailableJobsForRunner(e db.Engine, runner *ActionRunner) ([]*ActionRunJob, error) {
|
||||
jobCond := builder.NewCond()
|
||||
if runner.RepoID != 0 {
|
||||
jobCond = builder.Eq{"repo_id": runner.RepoID}
|
||||
} else if runner.OwnerID != 0 {
|
||||
jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
|
||||
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
|
||||
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
|
||||
}
|
||||
// Concurrency group checks for queuing one run behind the last run in the concurrency group are more
|
||||
// computationally expensive on the database. To manage the risk that this might have on large-scale deployments
|
||||
// When this feature is initially released, it can be disabled in the ini file by setting
|
||||
// `CONCURRENCY_GROUP_QUEUE_ENABLED = false` in the `[actions]` section. If disabled, then actions with a
|
||||
// concurrency group and `cancel-in-progress: false` will run simultaneously rather than being queued.
|
||||
if setting.Actions.ConcurrencyGroupQueueEnabled {
|
||||
jobCond = jobCond.And(getConcurrencyCondition())
|
||||
}
|
||||
if jobCond.IsValid() {
|
||||
// It is *likely* more efficient to use an EXISTS query here rather than an IN clause, as that allows the
|
||||
// database's query optimizer to perform partial computation of the subquery rather than complete computation.
|
||||
// However, database engines can be fickle and difficult to predict. We'll retain the original IN clause
|
||||
// implementation when ConcurrencyGroupQueueEnabled is disabled, which should maintain the same performance
|
||||
// characteristics. When ConcurrencyGroupQueueEnabled is enabled, it will switch to the EXISTS clause.
|
||||
if setting.Actions.ConcurrencyGroupQueueEnabled {
|
||||
jobCond = builder.Exists(builder.Select("id").From("action_run", "outer_run").
|
||||
Where(builder.Eq{"outer_run.id": builder.Expr("action_run_job.run_id")}).
|
||||
And(jobCond))
|
||||
} else {
|
||||
jobCond = builder.In("run_id", builder.Select("id").From("action_run", "outer_run").Where(jobCond))
|
||||
}
|
||||
}
|
||||
|
||||
var jobs []*ActionRunJob
|
||||
if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("updated", "id").Find(&jobs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
|
||||
ctx, commiter, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
|
@ -248,20 +330,8 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
|
|||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
jobCond := builder.NewCond()
|
||||
if runner.RepoID != 0 {
|
||||
jobCond = builder.Eq{"repo_id": runner.RepoID}
|
||||
} else if runner.OwnerID != 0 {
|
||||
jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
|
||||
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
|
||||
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
|
||||
}
|
||||
if jobCond.IsValid() {
|
||||
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))
|
||||
}
|
||||
|
||||
var jobs []*ActionRunJob
|
||||
if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("updated", "id").Find(&jobs); err != nil {
|
||||
jobs, err := GetAvailableJobsForRunner(e, runner)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
|
|
|
@ -127,6 +127,13 @@ func (t *AccessToken) DisplayPublicOnly() bool {
|
|||
return publicOnly
|
||||
}
|
||||
|
||||
// UpdateLastUsed updates the time this token was last used to now.
|
||||
func (t *AccessToken) UpdateLastUsed(ctx context.Context) error {
|
||||
t.UpdatedUnix = timeutil.TimeStampNow()
|
||||
_, err := db.GetEngine(ctx).ID(t.ID).Cols("updated_unix").NoAutoTime().Update(t)
|
||||
return err
|
||||
}
|
||||
|
||||
func getAccessTokenIDFromCache(token string) int64 {
|
||||
if successfulAccessTokenCache == nil {
|
||||
return 0
|
||||
|
@ -220,12 +227,6 @@ func (opts ListAccessTokensOptions) ToOrders() string {
|
|||
return "created_unix DESC"
|
||||
}
|
||||
|
||||
// UpdateAccessToken updates information of access token.
|
||||
func UpdateAccessToken(ctx context.Context, t *AccessToken) error {
|
||||
_, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAccessTokenByID deletes access token by given ID.
|
||||
func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
|
||||
cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{
|
||||
|
@ -258,5 +259,6 @@ func RegenerateAccessTokenByID(ctx context.Context, id, userID int64) (*AccessTo
|
|||
// Reset the creation time, token is unused
|
||||
t.UpdatedUnix = timeutil.TimeStampNow()
|
||||
|
||||
return t, UpdateAccessToken(ctx, t)
|
||||
_, err = db.GetEngine(ctx).ID(t.ID).Cols("token_salt", "token", "token_hash", "token_last_eight", "updated_unix").NoAutoTime().Update(t)
|
||||
return t, err
|
||||
}
|
||||
|
|
|
@ -5,10 +5,12 @@ package auth_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -107,14 +109,17 @@ func TestListAccessTokens(t *testing.T) {
|
|||
assert.Empty(t, tokens)
|
||||
}
|
||||
|
||||
func TestUpdateAccessToken(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c")
|
||||
require.NoError(t, err)
|
||||
token.Name = "Token Z"
|
||||
func TestUpdateLastUsed(t *testing.T) {
|
||||
timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
defer timeutil.MockUnset()
|
||||
|
||||
require.NoError(t, auth_model.UpdateAccessToken(db.DefaultContext, token))
|
||||
unittest.AssertExistsAndLoadBean(t, token)
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
token := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2})
|
||||
|
||||
require.NoError(t, token.UpdateLastUsed(t.Context()))
|
||||
|
||||
token = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2})
|
||||
assert.Equal(t, timeutil.TimeStampNow(), token.UpdatedUnix)
|
||||
}
|
||||
|
||||
func TestDeleteAccessTokenByID(t *testing.T) {
|
||||
|
|
|
@ -298,6 +298,31 @@ func TruncateBeans(ctx context.Context, beans ...any) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// TruncateBeansCascade deletes all given beans. Beans MUST NOT contain delete conditions, as tables related by foreign
|
||||
// keys will also be truncated.
|
||||
func TruncateBeansCascade(ctx context.Context, beans ...any) (err error) {
|
||||
// Expand the list of beans to any other table with a foreign key reference to the beans
|
||||
cascadeTables, err := extendBeansForCascade(beans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sort the beans in inverse foreign key delete order
|
||||
cascadeSorted, err := sortBeans(cascadeTables, foreignKeySortDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute the truncate
|
||||
e := GetEngine(ctx)
|
||||
for i := range cascadeSorted {
|
||||
if _, err = e.Truncate(cascadeSorted[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
|
||||
func CountByBean(ctx context.Context, bean any) (int64, error) {
|
||||
return GetEngine(ctx).Count(bean)
|
||||
|
|
|
@ -161,9 +161,13 @@ func (w engineGroupWrapper) AddHook(hook contexts.Hook) bool {
|
|||
|
||||
// SyncAllTables sync the schemas of all tables
|
||||
func SyncAllTables() error {
|
||||
_, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
|
||||
sortedTables, err := sortBeans(tables, foreignKeySortInsert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
|
||||
WarnIfDatabaseColumnMissed: true,
|
||||
}, tables...)
|
||||
}, sortedTables...)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
275
models/db/foreign_keys.go
Normal file
275
models/db/foreign_keys.go
Normal file
|
@ -0,0 +1,275 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"forgejo.org/modules/container"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type schemaWithDefaultBean struct {
|
||||
schema *schemas.Table
|
||||
bean any
|
||||
}
|
||||
|
||||
var (
|
||||
cachedForeignKeyOrderedTables = sync.OnceValues(foreignKeyOrderedTables)
|
||||
cachedTableNameLookupOrder = sync.OnceValues(tableNameLookupOrder)
|
||||
// Slice of all registered tables, including their bean from `RegisterModel()` and their schemas.Table reference.
|
||||
cachedSchemaTables = sync.OnceValues(func() ([]schemaWithDefaultBean, error) {
|
||||
schemaTables := make([]schemaWithDefaultBean, 0, len(tables))
|
||||
for _, bean := range tables {
|
||||
table, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cachedSchemaTables: failure to fetch schema table for bean %#v: %w", bean, err)
|
||||
}
|
||||
schemaTables = append(schemaTables, schemaWithDefaultBean{
|
||||
schema: table,
|
||||
bean: bean,
|
||||
})
|
||||
}
|
||||
return schemaTables, nil
|
||||
})
|
||||
// Lookup map from table name -> {schemas.Table, bean}. The bean is the empty bean from `RegisterModel()`.
|
||||
cachedTableMap = sync.OnceValues(func() (map[string]schemaWithDefaultBean, error) {
|
||||
schemaTables, err := cachedSchemaTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retval := make(map[string]schemaWithDefaultBean, len(schemaTables))
|
||||
for _, table := range schemaTables {
|
||||
retval[table.schema.Name] = table
|
||||
}
|
||||
return retval, nil
|
||||
})
|
||||
// Table A has foreign keys to [B, C], this is a map of A -> {B, C}.
|
||||
cachedReferencingTables = sync.OnceValues(func() (map[string][]string, error) {
|
||||
schemaTables, err := cachedSchemaTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return calculateReferencingTables(schemaTables), nil
|
||||
})
|
||||
// Table A has foreign keys to [B, C], this is a map of B -> {A}, C -> {A}.
|
||||
cachedReferencedTables = sync.OnceValues(func() (map[string][]string, error) {
|
||||
referencingTables, err := cachedReferencingTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
referencedTables := make(map[string][]string)
|
||||
for referencingTable, targetTables := range referencingTables {
|
||||
for _, targetTable := range targetTables {
|
||||
referencedTables[targetTable] = append(referencedTables[targetTable], referencingTable)
|
||||
}
|
||||
}
|
||||
return referencedTables, nil
|
||||
})
|
||||
)
|
||||
|
||||
// Create a map for each schema table which contains a slice of all the tables that reference it (with a foreign key).
|
||||
func calculateReferencingTables(tables []schemaWithDefaultBean) map[string][]string {
|
||||
referencingTables := make(map[string][]string, len(tables))
|
||||
for _, table := range tables {
|
||||
tableName := table.schema.Name
|
||||
for _, fk := range table.schema.ForeignKeys {
|
||||
referencingTables[tableName] = append(referencingTables[tableName], fk.TargetTableName)
|
||||
}
|
||||
}
|
||||
return referencingTables
|
||||
}
|
||||
|
||||
// Create a list of database tables in their "foreign key order". This order specifies the safe insertion order for
|
||||
// records into tables, where earlier tables in the list are referenced by foreign keys that exist in tables later in
|
||||
// the list. This order can be used in reverse as a safe deletion order as well.
|
||||
//
|
||||
// An ordered list of tables is incompatible with tables that have self-referencing foreign keys and circular referenced
|
||||
// foreign keys; however neither of those cases are in-use in Forgejo.
|
||||
func calculateTableForeignKeyOrder(tables []schemaWithDefaultBean) ([]schemaWithDefaultBean, error) {
|
||||
remainingTables := slices.Clone(tables)
|
||||
|
||||
referencingTables := calculateReferencingTables(remainingTables)
|
||||
orderedTables := make([]schemaWithDefaultBean, 0, len(remainingTables))
|
||||
|
||||
for len(remainingTables) > 0 {
|
||||
nextGroup := make([]schemaWithDefaultBean, 0, len(remainingTables))
|
||||
|
||||
for _, targetTable := range remainingTables {
|
||||
// Skip if this targetTable has foreign keys and the target table hasn't been created.
|
||||
slice, ok := referencingTables[targetTable.schema.Name]
|
||||
if ok && len(slice) > 0 { // This table is still referencing an uncreated table
|
||||
continue
|
||||
}
|
||||
// This table's references are satisfied or it had none
|
||||
nextGroup = append(nextGroup, targetTable)
|
||||
}
|
||||
|
||||
if len(nextGroup) == 0 {
|
||||
return nil, fmt.Errorf("calculateTableForeignKeyOrder: unable to figure out next table from remainingTables = %#v", remainingTables)
|
||||
}
|
||||
|
||||
orderedTables = append(orderedTables, nextGroup...)
|
||||
|
||||
// Cleanup between loops: remove each table in nextGroup from remainingTables, and remove their table names from
|
||||
// referencingTables as well.
|
||||
for _, doneTable := range nextGroup {
|
||||
remainingTables = slices.DeleteFunc(remainingTables, func(remainingTable schemaWithDefaultBean) bool {
|
||||
return remainingTable.schema.Name == doneTable.schema.Name
|
||||
})
|
||||
for referencingTable, referencedTables := range referencingTables {
|
||||
referencingTables[referencingTable] = slices.DeleteFunc(referencedTables, func(tableName string) bool {
|
||||
return tableName == doneTable.schema.Name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return orderedTables, nil
|
||||
}
|
||||
|
||||
// Create a list of registered database tables in their "foreign key order", per calculateTableForeignKeyOrder.
|
||||
func foreignKeyOrderedTables() ([]schemaWithDefaultBean, error) {
|
||||
schemaTables, err := cachedSchemaTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderedTables, err := calculateTableForeignKeyOrder(schemaTables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return orderedTables, nil
|
||||
}
|
||||
|
||||
// Create a map from each registered database table's name to its order in "foreign key order", per
|
||||
// calculateTableForeignKeyOrder.
|
||||
func tableNameLookupOrder() (map[string]int, error) {
|
||||
tables, err := cachedForeignKeyOrderedTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lookupMap := make(map[string]int, len(tables))
|
||||
for i, table := range tables {
|
||||
lookupMap[table.schema.Name] = i
|
||||
}
|
||||
|
||||
return lookupMap, nil
|
||||
}
|
||||
|
||||
// When used as a comparator function in `slices.SortFunc`, can sort a slice into the safe insertion order for records
|
||||
// in tables, where earlier tables in the list are referenced by foreign keys that exist in tables later in the list.
|
||||
func TableNameInsertionOrderSortFunc(table1, table2 string) int {
|
||||
lookupMap, err := cachedTableNameLookupOrder()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cachedTableNameLookupOrder failed: %#v", err))
|
||||
}
|
||||
|
||||
// Since this is typically used by `slices.SortFunc` it can't return an error. If a table is referenced that isn't
|
||||
// a registered model then it will be sorted at the beginning -- this case is used in models/gitea_migrations/test.
|
||||
val1, ok := lookupMap[table1]
|
||||
if !ok {
|
||||
val1 = -1
|
||||
}
|
||||
val2, ok := lookupMap[table2]
|
||||
if !ok {
|
||||
val2 = -1
|
||||
}
|
||||
|
||||
return cmp.Compare(val1, val2)
|
||||
}
|
||||
|
||||
// In "Insert" order, tables that have a foreign key will be sorted after the tables that the foreign key points to, so
|
||||
// that records can be safely inserted in this order. "Delete" order is the opposite, and allows records to be safely
|
||||
// deleted in this order.
|
||||
type foreignKeySortOrder int8
|
||||
|
||||
const (
|
||||
foreignKeySortInsert foreignKeySortOrder = iota
|
||||
foreignKeySortDelete
|
||||
)
|
||||
|
||||
// Sort the provided beans in the provided foreign-key sort order.
|
||||
func sortBeans(beans []any, sortOrder foreignKeySortOrder) ([]any, error) {
|
||||
type beanWithTableName struct {
|
||||
bean any
|
||||
tableName string
|
||||
}
|
||||
|
||||
beansWithTableNames := make([]beanWithTableName, 0, len(beans))
|
||||
for _, bean := range beans {
|
||||
table, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sortBeans: failure to fetch schema table for bean %#v: %w", bean, err)
|
||||
}
|
||||
beansWithTableNames = append(beansWithTableNames, beanWithTableName{bean: bean, tableName: table.Name})
|
||||
}
|
||||
|
||||
slices.SortFunc(beansWithTableNames, func(a, b beanWithTableName) int {
|
||||
if sortOrder == foreignKeySortInsert {
|
||||
return TableNameInsertionOrderSortFunc(a.tableName, b.tableName)
|
||||
}
|
||||
return TableNameInsertionOrderSortFunc(b.tableName, a.tableName)
|
||||
})
|
||||
|
||||
beanRetval := make([]any, len(beans))
|
||||
for i, beanWithTableName := range beansWithTableNames {
|
||||
beanRetval[i] = beanWithTableName.bean
|
||||
}
|
||||
return beanRetval, nil
|
||||
}
|
||||
|
||||
// A database operation on `beans` may need to affect additional tables based upon foreign keys to those beans.
|
||||
// extendBeansForCascade returns a new list of beans which includes all the referencing tables that link to `beans`'
|
||||
// tables. For example, provided a `&User{}`, it will return `[&User{}, &Stopwatch{}, ...]` where `Stopwatch` is a table
|
||||
// that references `User`. The additional beans returned will be default structs that were provided to
|
||||
// `db.RegisterModel`.
|
||||
func extendBeansForCascade(beans []any) ([]any, error) {
|
||||
referencedTables, err := cachedReferencedTables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tableMap, err := cachedTableMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deduplicateTables := make(container.Set[string], len(beans))
|
||||
|
||||
finalBeans := slices.Clone(beans)
|
||||
newBeans := beans
|
||||
nextBeanSet := make([]any, 0)
|
||||
|
||||
for {
|
||||
for _, bean := range newBeans {
|
||||
schema, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cascadeDeleteTables: failure to fetch schema table for bean %#v: %w", bean, err)
|
||||
}
|
||||
if deduplicateTables.Contains(schema.Name) {
|
||||
continue
|
||||
}
|
||||
deduplicateTables.Add(schema.Name)
|
||||
for _, referencingTable := range referencedTables[schema.Name] {
|
||||
table := tableMap[referencingTable]
|
||||
finalBeans = append(finalBeans, table.bean)
|
||||
nextBeanSet = append(nextBeanSet, table.bean)
|
||||
}
|
||||
}
|
||||
|
||||
if len(nextBeanSet) == 0 {
|
||||
break
|
||||
}
|
||||
newBeans = nextBeanSet
|
||||
nextBeanSet = nextBeanSet[:0] // set len 0, keep allocation for reuse
|
||||
}
|
||||
|
||||
return finalBeans, nil
|
||||
}
|
|
@ -5,39 +5,82 @@ package db
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Iterate iterate all the Bean object
|
||||
// Iterate iterate all the Bean object. The table being iterated must have a single-column primary key.
|
||||
func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
|
||||
var start int
|
||||
var dummy Bean
|
||||
batchSize := setting.Database.IterateBufferSize
|
||||
sess := GetEngine(ctx)
|
||||
|
||||
table, err := TableInfo(&dummy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch table info for bean %v: %w", dummy, err)
|
||||
}
|
||||
if len(table.PrimaryKeys) != 1 {
|
||||
return fmt.Errorf("iterate only supported on a table with 1 primary key field, but table %s had %d", table.Name, len(table.PrimaryKeys))
|
||||
}
|
||||
|
||||
pkDbName := table.PrimaryKeys[0]
|
||||
var pkStructFieldName string
|
||||
|
||||
for _, c := range table.Columns() {
|
||||
if c.Name == pkDbName {
|
||||
pkStructFieldName = c.FieldName
|
||||
break
|
||||
}
|
||||
}
|
||||
if pkStructFieldName == "" {
|
||||
return fmt.Errorf("iterate unable to identify struct field for primary key %s", pkDbName)
|
||||
}
|
||||
|
||||
var lastPK any
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
beans := make([]*Bean, 0, batchSize)
|
||||
|
||||
sess := GetEngine(ctx)
|
||||
sess = sess.OrderBy(pkDbName)
|
||||
if cond != nil {
|
||||
sess = sess.Where(cond)
|
||||
}
|
||||
if err := sess.Limit(batchSize, start).Find(&beans); err != nil {
|
||||
if lastPK != nil {
|
||||
sess = sess.Where(builder.Gt{pkDbName: lastPK})
|
||||
}
|
||||
|
||||
if err := sess.Limit(batchSize).Find(&beans); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(beans) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(beans)
|
||||
|
||||
for _, bean := range beans {
|
||||
if err := f(ctx, bean); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
lastBean := beans[len(beans)-1]
|
||||
lastPK = extractFieldValue(lastBean, pkStructFieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extractFieldValue(bean any, fieldName string) any {
|
||||
v := reflect.ValueOf(bean)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
field := v.FieldByName(fieldName)
|
||||
return field.Interface()
|
||||
}
|
||||
|
|
|
@ -5,42 +5,113 @@ package db_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestIterate(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
db.SetLogSQL(t.Context(), true)
|
||||
defer test.MockVariableValue(&setting.Database.IterateBufferSize, 50)()
|
||||
|
||||
cnt, err := db.GetEngine(db.DefaultContext).Count(&repo_model.RepoUnit{})
|
||||
require.NoError(t, err)
|
||||
t.Run("No Modifications", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
|
||||
var repoUnitCnt int
|
||||
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
repoUnitCnt++
|
||||
return nil
|
||||
// Fetch all the repo unit IDs...
|
||||
var remainingRepoIDs []int64
|
||||
db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs)
|
||||
|
||||
// Ensure that every repo unit ID is found when doing iterate:
|
||||
err = db.Iterate(t.Context(), nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
return repo.ID == n
|
||||
})
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remainingRepoIDs)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, cnt, repoUnitCnt)
|
||||
|
||||
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repoUnit *repo_model.RepoUnit) error {
|
||||
has, err := db.ExistByID[repo_model.RepoUnit](ctx, repoUnit.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID}
|
||||
}
|
||||
return nil
|
||||
t.Run("Concurrent Delete", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
|
||||
// Fetch all the repo unit IDs...
|
||||
var remainingRepoIDs []int64
|
||||
db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs)
|
||||
|
||||
// Ensure that every repo unit ID is found, even if someone else performs a DELETE on the table while we're
|
||||
// iterating. In real-world usage the deleted record may or may not be returned, but the important
|
||||
// subject-under-test is that no *other* record is skipped.
|
||||
didDelete := false
|
||||
err = db.Iterate(t.Context(), nil, func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
// While on page 2 (assuming ID ordering, 50 record buffer size)...
|
||||
if repo.ID == 51 {
|
||||
// Delete a record that would have been on page 1.
|
||||
affected, err := db.GetEngine(t.Context()).ID(25).Delete(&repo_model.RepoUnit{})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if affected != 1 {
|
||||
return fmt.Errorf("expected to delete 1 record, but affected %d records", affected)
|
||||
}
|
||||
didDelete = true
|
||||
}
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
return repo.ID == n
|
||||
})
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, didDelete, "didDelete")
|
||||
assert.Empty(t, remainingRepoIDs)
|
||||
})
|
||||
|
||||
t.Run("Verify cond applied", func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
xe, err := unittest.GetXORMEngine()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
|
||||
|
||||
// Fetch all the repo unit IDs...
|
||||
var remainingRepoIDs []int64
|
||||
db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs)
|
||||
|
||||
// Remove those that we're not expecting to find based upon `Iterate`'s condition. We'll trim the front few
|
||||
// records and last few records, which will confirm that cond is applied on all pages.
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
return n <= 15 || n > 1000
|
||||
})
|
||||
err = db.Iterate(t.Context(), builder.Gt{"id": 15}.And(builder.Lt{"id": 1000}), func(ctx context.Context, repo *repo_model.RepoUnit) error {
|
||||
removedRecord := false
|
||||
// Remove the record from remainingRepoIDs, but track to make sure we did actually remove a record
|
||||
remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool {
|
||||
if repo.ID == n {
|
||||
removedRecord = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if !removedRecord {
|
||||
return fmt.Errorf("unable to find record in remainingRepoIDs for repo %d, indicating a cond application failure", repo.ID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remainingRepoIDs)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -525,6 +525,7 @@
|
|||
ref: "refs/heads/main"
|
||||
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
|
||||
event: "workflow_dispatch"
|
||||
trigger_event: "workflow_dispatch"
|
||||
is_fork_pull_request: 0
|
||||
status: 6 # running
|
||||
started: 1683636528
|
||||
|
|
|
@ -349,7 +349,7 @@
|
|||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: false
|
||||
is_fork: true
|
||||
fork_id: 10
|
||||
is_template: false
|
||||
template_id: 0
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
-
|
||||
id: 4
|
||||
user_id: -1
|
||||
issue_id: 4
|
||||
time: 1
|
||||
created_unix: 946684803
|
||||
|
|
|
@ -1562,3 +1562,41 @@
|
|||
theme: ""
|
||||
keep_activity_private: false
|
||||
created_unix: 1672578400
|
||||
|
||||
-
|
||||
id: 42
|
||||
lower_name: federated-example.net
|
||||
name: federated-example.net
|
||||
full_name: federated
|
||||
email: f73240e82-c061-41ef-b7d6-4376cb6f2e1c@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: federated-example.net
|
||||
type: 5
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
is_active: false
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
allow_git_hook: false
|
||||
allow_import_local: false
|
||||
allow_create_organization: false
|
||||
prohibit_login: false
|
||||
avatar: ""
|
||||
avatar_email: f73240e82-c061-41ef-b7d6-4376cb6f2e1c@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
created_unix: 1759086716
|
||||
|
|
105
models/forgejo_migrations/README.md
Normal file
105
models/forgejo_migrations/README.md
Normal file
|
@ -0,0 +1,105 @@
|
|||
# forgejo_migrations
|
||||
|
||||
Forgejo has three database migration mechanisms:
|
||||
|
||||
- `models/gitea_migrations`
|
||||
- Original database schema migrations from Forgejo's legacy as a fork of Gitea.
|
||||
- A linear set of migrations referenced in `models/gitea_migrations/migrations.go`, each represented by a number (eg. migration 304).
|
||||
- The current version is recorded in the database in the table `version`.
|
||||
- `models/forgejo_migrations_legacy`
|
||||
- The next 50-ish database schema migrations reflecting change in Forgejo's database structure between Forgejo v7.0 and v14.0
|
||||
- A linear set of migrations referenced in `models/forgejo_migrations_legacy/migrate.go`, each represented by a number (eg. migration 43).
|
||||
- The current version is recorded in the database in the table `forgejo_version`.
|
||||
- `models/forgejo_migrations`
|
||||
- The most recent database schema migrations, reflecting change in the v14.0 release cycle and onwards into the future.
|
||||
- Each migration is identified by the filename it is stored in.
|
||||
- The applied migrations are recorded in the database in the table `forgejo_migration`.
|
||||
|
||||
`forgejo_migrations` is designed to reduce code conflicts when multiple developers may be making schema migrations in close succession, which it does by avoiding having one code file with a long array of migrations. Instead, each file in `models/forgejo_migrations` registers itself as a migration, and its filename indicates the order that migration will be applied.
|
||||
|
||||
Files in `forgejo_migrations` must:
|
||||
- Define an `init` function which registers a function to be invoked for the migration.
|
||||
- Follow the naming convention:
|
||||
- The letter `v`
|
||||
- A number, representing the development cycle that the migration was created in
|
||||
- A letter, indicating any required migration ordering
|
||||
- The character `_` (underscore)
|
||||
- A short descriptive identifier for the migration
|
||||
|
||||
For example, valid migration file names would look like this:
|
||||
- `v14a_add-threaded-comments.go`
|
||||
- `v14a_add-federated-emojis.go`
|
||||
- `v14b_fix-threaded-comments-index.go`
|
||||
|
||||
|
||||
## Migration Ordering
|
||||
|
||||
Forgejo executes registered migrations in `forgejo_migrations` in the `strings.Compare()` ordering of their filename.
|
||||
|
||||
There are edge cases where migrations may not be executed in this exact order:
|
||||
- If a schema change is backported to an earlier Forgejo release. For example, if a bugfix during the v15 development cycle was backported into a v14 patch release, then a migration labeled `v15a_fix-unusual-data-corruption.go` could be applied during a v14 software upgrade. In the future when a v15 software release occurs, that migration will be identified as already applied and will be skipped.
|
||||
- If a developer working on Forgejo switches between different branches with different schema migrations.
|
||||
- If the contents of the `forgejo_migrations` database table are changed outside of Forgejo modifying it.
|
||||
|
||||
|
||||
## Creating a new Migration
|
||||
|
||||
First, determine the filename for your migration. In general, you create a new migration by starting a file with the same prefix as the most recent migration present. If `v14a_add-forgejo-migrations-table.go` was the last file, most of the time you can create your migration with the same `v14a_...` prefix.
|
||||
|
||||
There are two exceptions:
|
||||
- After the release branch is cut for a release, increment the version in the migration. If v14 was cut, you would start `v15a_...` as the next migration.
|
||||
- If your migration requires that an earlier migration is complete first, you would increment the letter in the prefix. If you were modifying the table created by `v14a_add-forgejo-migrations-table.go`, then you would name your migration `v14b_...`.
|
||||
|
||||
Once you've determined the migration filename, then you can copy this template into the file:
|
||||
|
||||
```go
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMigration(&Migration{
|
||||
Description: "short sentence describing this migration",
|
||||
Upgrade: myMigrationFunction, // rename
|
||||
})
|
||||
}
|
||||
|
||||
func myMigrationFunction(x *xorm.Engine) error {
|
||||
// add migration logic here
|
||||
//
|
||||
// to prevent `make watch` from recording this migration as done when it
|
||||
// isn't authored yet, returh an error until the implementation is done
|
||||
return errors.New("not implemented yet")
|
||||
}
|
||||
```
|
||||
|
||||
And now it's up to you to write the contents of your migration function.
|
||||
|
||||
|
||||
## Development Notes
|
||||
|
||||
Once migrations are executed, a record of their execution is stored in the database table `forgejo_migration`.
|
||||
|
||||
```sql
|
||||
=> SELECT * FROM forgejo_migration;
|
||||
id | created_unix
|
||||
-----------------------------------+--------------
|
||||
v14a_add-forgejo-migrations-table | 1760402451
|
||||
v14a_example-other-migration | 1760402453
|
||||
v14b_another-example | 1760402455
|
||||
v15a_add-something-cool | 1760402456
|
||||
v15a_another-example-again | 1760402457
|
||||
```
|
||||
|
||||
If your migration successfully executes once, it will be recorded in this table and it will never execute again, even if you change the migration code. It is common during development to need to re-run a migration, in which case you can delete the record that you're working on developing. The migration will be re-run as soon as the Forgejo server is restarted:
|
||||
|
||||
```sql
|
||||
=> DELETE FROM forgejo_migration WHERE id = 'v15a_another-example-again';
|
||||
```
|
31
models/forgejo_migrations/foreign_key_utils.go
Normal file
31
models/forgejo_migrations/foreign_key_utils.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"forgejo.org/modules/log"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func syncDoctorForeignKey(x *xorm.Engine, beans []any) error {
|
||||
for _, bean := range beans {
|
||||
// Sync() drops indexes by default, which will cause unnecessary rebuilding of indexes when syncDoctorForeignKey
|
||||
// is used with partial bean definitions; so we disable that option
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean)
|
||||
if err != nil {
|
||||
if errors.Is(err, xorm.ErrForeignKeyViolation) {
|
||||
tableName := x.TableName(bean)
|
||||
log.Error(
|
||||
"Foreign key creation on table %s failed. Run `forgejo doctor check --all` to identify the orphaned records preventing this foreign key from being created. Error was: %v",
|
||||
tableName, err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
migration_tests "forgejo.org/models/migrations/test"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -1,229 +1,222 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/forgejo/semver"
|
||||
forgejo_v1_20 "forgejo.org/models/forgejo_migrations/v1_20"
|
||||
forgejo_v1_22 "forgejo.org/models/forgejo_migrations/v1_22"
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
// ForgejoVersion describes the Forgejo version table. Should have only one row with id = 1.
|
||||
type ForgejoVersion struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64
|
||||
// ForgejoMigration table contains a record of migrations applied to the database. (Note that there are older
|
||||
// migrations in the forgejo_version table from before this table was introduced, and the `version` table from Gitea
|
||||
// migrations). Each record in this table represents one successfully completed migration which was completed at the
|
||||
// `CreatedUnix` time.
|
||||
type ForgejoMigration struct {
|
||||
ID string `xorm:"pk"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
type Migration struct {
|
||||
description string
|
||||
migrate func(*xorm.Engine) error
|
||||
Description string // short plaintext explanation of the migration
|
||||
Upgrade func(*xorm.Engine) error // perform the migration
|
||||
|
||||
id string // unique migration identifier
|
||||
}
|
||||
|
||||
// NewMigration creates a new migration.
|
||||
func NewMigration(desc string, fn func(*xorm.Engine) error) *Migration {
|
||||
return &Migration{desc, fn}
|
||||
var (
|
||||
rawMigrations []*Migration
|
||||
migrationFilenameRegex = regexp.MustCompile(`/(?P<migration_group>v[0-9]+[a-z])_(?P<migration_id>[^/]+)\.go$`)
|
||||
)
|
||||
|
||||
var getMigrationFilename = func() string {
|
||||
_, migrationFilename, _, _ := runtime.Caller(2)
|
||||
return migrationFilename
|
||||
}
|
||||
|
||||
// This is a sequence of additional Forgejo migrations.
|
||||
// Add new migrations to the bottom of the list.
|
||||
var migrations = []*Migration{
|
||||
// v0 -> v1
|
||||
NewMigration("Create the `forgejo_blocked_user` table", forgejo_v1_20.AddForgejoBlockedUser),
|
||||
// v1 -> v2
|
||||
NewMigration("Create the `forgejo_sem_ver` table", forgejo_v1_20.CreateSemVerTable),
|
||||
// v2 -> v3
|
||||
NewMigration("Create the `forgejo_auth_token` table", forgejo_v1_20.CreateAuthorizationTokenTable),
|
||||
// v3 -> v4
|
||||
NewMigration("Add the `default_permissions` column to the `repo_unit` table", forgejo_v1_22.AddDefaultPermissionsToRepoUnit),
|
||||
// v4 -> v5
|
||||
NewMigration("Create the `forgejo_repo_flag` table", forgejo_v1_22.CreateRepoFlagTable),
|
||||
// v5 -> v6
|
||||
NewMigration("Add the `wiki_branch` column to the `repository` table", forgejo_v1_22.AddWikiBranchToRepository),
|
||||
// v6 -> v7
|
||||
NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
|
||||
// v7 -> v8
|
||||
NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes),
|
||||
// v8 -> v9
|
||||
NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting),
|
||||
// v9 -> v10
|
||||
NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser),
|
||||
// v10 -> v11
|
||||
NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue),
|
||||
// v11 -> v12
|
||||
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount),
|
||||
// v12 -> v13
|
||||
NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease),
|
||||
// v13 -> v14
|
||||
NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge),
|
||||
// v14 -> v15
|
||||
NewMigration("Create the `federation_host` table", CreateFederationHostTable),
|
||||
// v15 -> v16
|
||||
NewMigration("Create the `federated_user` table", CreateFederatedUserTable),
|
||||
// v16 -> v17
|
||||
NewMigration("Add `normalized_federated_uri` column to `user` table", AddNormalizedFederatedURIToUser),
|
||||
// v17 -> v18
|
||||
NewMigration("Create the `following_repo` table", CreateFollowingRepoTable),
|
||||
// v18 -> v19
|
||||
NewMigration("Add external_url to attachment table", AddExternalURLColumnToAttachmentTable),
|
||||
// v19 -> v20
|
||||
NewMigration("Creating Quota-related tables", CreateQuotaTables),
|
||||
// v20 -> v21
|
||||
NewMigration("Add SSH keypair to `pull_mirror` table", AddSSHKeypairToPushMirror),
|
||||
// v21 -> v22
|
||||
NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential),
|
||||
// v22 -> v23
|
||||
NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge),
|
||||
// v23 -> v24
|
||||
NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken),
|
||||
// v24 -> v25
|
||||
NewMigration("Migrate `secret` column to store keying material", MigrateTwoFactorToKeying),
|
||||
// v25 -> v26
|
||||
NewMigration("Add `hash_blake2b` column to `package_blob` table", AddHashBlake2bToPackageBlob),
|
||||
// v26 -> v27
|
||||
NewMigration("Add `created_unix` column to `user_redirect` table", AddCreatedUnixToRedirect),
|
||||
// v27 -> v28
|
||||
NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser),
|
||||
// v28 -> v29
|
||||
NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation),
|
||||
// v29 -> v30
|
||||
NewMigration("Migrate `User.NormalizedFederatedURI` column to extract port & schema into FederatedHost", MigrateNormalizedFederatedURI),
|
||||
// v30 -> v31
|
||||
NewMigration("Normalize repository.topics to empty slice instead of null", SetTopicsAsEmptySlice),
|
||||
// v31 -> v32
|
||||
NewMigration("Migrate maven package name concatenation", ChangeMavenArtifactConcatenation),
|
||||
// v32 -> v33
|
||||
NewMigration("Add federated user activity tables, update the `federated_user` table & add indexes", FederatedUserActivityMigration),
|
||||
// v33 -> v34
|
||||
NewMigration("Add `notify-email` column to `action_run` table", AddNotifyEmailToActionRun),
|
||||
// v34 -> v35
|
||||
NewMigration("Noop because of https://codeberg.org/forgejo/forgejo/issues/8373", NoopAddIndexToActionRunStopped),
|
||||
// v35 -> v36
|
||||
NewMigration("Fix wiki unit default permission", FixWikiUnitDefaultPermission),
|
||||
// v36 -> v37
|
||||
NewMigration("Add `branch_filter` to `push_mirror` table", AddPushMirrorBranchFilter),
|
||||
// v37 -> v38
|
||||
NewMigration("Add `resolved_unix` column to `abuse_report` table", AddResolvedUnixToAbuseReport),
|
||||
// v38 -> v39
|
||||
NewMigration("Migrate `data` column of `secret` table to store keying material", MigrateActionSecretsToKeying),
|
||||
// v39 -> v40
|
||||
NewMigration("Add index for release sha1", AddIndexForReleaseSha1),
|
||||
}
|
||||
func registerMigration(migration *Migration) {
|
||||
migrationFilename := getMigrationFilename()
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
func GetCurrentDBVersion(x *xorm.Engine) (int64, error) {
|
||||
if err := x.Sync(new(ForgejoVersion)); err != nil {
|
||||
return -1, fmt.Errorf("sync: %w", err)
|
||||
if migrationResolutionComplete {
|
||||
panic(fmt.Sprintf("attempted to register migration from %s after migration resolution is already complete", migrationFilename))
|
||||
}
|
||||
|
||||
currentVersion := &ForgejoVersion{ID: 1}
|
||||
has, err := x.Get(currentVersion)
|
||||
matches := migrationFilenameRegex.FindStringSubmatch(migrationFilename)
|
||||
if len(matches) == 0 {
|
||||
panic(fmt.Sprintf("registerMigration must be invoked from a file matching migrationFilenameRegex, but was invoked from %q", migrationFilename))
|
||||
}
|
||||
migration.id = fmt.Sprintf("%s_%s", matches[1], matches[2]) // this just rebuilds the filename, but guarantees that the regex applied for consistent naming
|
||||
|
||||
rawMigrations = append(rawMigrations, migration)
|
||||
}
|
||||
|
||||
// For testing only
|
||||
func resetMigrations() {
|
||||
rawMigrations = nil
|
||||
orderedMigrations = nil
|
||||
migrationResolutionComplete = false
|
||||
inMemoryMigrationIDs = nil
|
||||
}
|
||||
|
||||
var (
|
||||
migrationResolutionComplete = false
|
||||
inMemoryMigrationIDs container.Set[string]
|
||||
orderedMigrations []*Migration
|
||||
)
|
||||
|
||||
func resolveMigrations() {
|
||||
if migrationResolutionComplete {
|
||||
return
|
||||
}
|
||||
|
||||
inMemoryMigrationIDs = make(container.Set[string])
|
||||
for _, m := range rawMigrations {
|
||||
if inMemoryMigrationIDs.Contains(m.id) {
|
||||
// With the filename-based migration ID this shouldn't be possible, but a bit of a sanity check..
|
||||
panic(fmt.Sprintf("migration id is duplicated: %q", m.id))
|
||||
}
|
||||
inMemoryMigrationIDs.Add(m.id)
|
||||
}
|
||||
|
||||
orderedMigrations = slices.Clone(rawMigrations)
|
||||
slices.SortFunc(orderedMigrations, func(m1, m2 *Migration) int {
|
||||
return strings.Compare(m1.id, m2.id)
|
||||
})
|
||||
|
||||
migrationResolutionComplete = true
|
||||
}
|
||||
|
||||
func getInDBMigrationIDs(x *xorm.Engine) (container.Set[string], error) {
|
||||
var inDBMigrations []ForgejoMigration
|
||||
err := x.Find(&inDBMigrations)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("get: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return -1, nil
|
||||
}
|
||||
return currentVersion.Version, nil
|
||||
}
|
||||
|
||||
// ExpectedVersion returns the expected Forgejo database version.
|
||||
func ExpectedVersion() int64 {
|
||||
return int64(len(migrations))
|
||||
inDBMigrationIDs := make(container.Set[string], len(inDBMigrations))
|
||||
for _, inDB := range inDBMigrations {
|
||||
inDBMigrationIDs.Add(inDB.ID)
|
||||
}
|
||||
|
||||
return inDBMigrationIDs, nil
|
||||
}
|
||||
|
||||
// EnsureUpToDate will check if the Forgejo database is at the correct version.
|
||||
func EnsureUpToDate(x *xorm.Engine) error {
|
||||
currentDB, err := GetCurrentDBVersion(x)
|
||||
resolveMigrations()
|
||||
|
||||
inDBMigrationIDs, err := getInDBMigrationIDs(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentDB < 0 {
|
||||
return errors.New("database has not been initialized")
|
||||
// invalidMigrations are those that are in the database, but aren't registered.
|
||||
invalidMigrations := inDBMigrationIDs.Difference(inMemoryMigrationIDs)
|
||||
if len(invalidMigrations) > 0 {
|
||||
return fmt.Errorf("current Forgejo database has migration(s) %s applied, which are not registered migrations", strings.Join(invalidMigrations.Slice(), ", "))
|
||||
}
|
||||
|
||||
expected := ExpectedVersion()
|
||||
|
||||
if currentDB != expected {
|
||||
return fmt.Errorf(`current Forgejo database version %d is not equal to the expected version %d. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expected)
|
||||
// unappliedMigrations are those that haven't yet been applied, but seem valid
|
||||
unappliedMigrations := inMemoryMigrationIDs.Difference(inDBMigrationIDs)
|
||||
if len(unappliedMigrations) > 0 {
|
||||
return fmt.Errorf(`current Forgejo database is missing migration(s) %s. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, strings.Join(unappliedMigrations.Slice(), ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func recordMigrationComplete(x *xorm.Engine, migration *Migration) error {
|
||||
affected, err := x.Insert(&ForgejoMigration{ID: migration.id})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if affected != 1 {
|
||||
return fmt.Errorf("migration[%s]: failed to insert into DB, %d records affected", migration.id, affected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Migrate Forgejo database to current version.
|
||||
func Migrate(x *xorm.Engine) error {
|
||||
func Migrate(x *xorm.Engine, freshDB bool) error {
|
||||
resolveMigrations()
|
||||
|
||||
// Set a new clean the default mapper to GonicMapper as that is the default for .
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync(new(ForgejoVersion)); err != nil {
|
||||
if err := x.Sync(new(ForgejoMigration)); err != nil {
|
||||
return fmt.Errorf("sync: %w", err)
|
||||
}
|
||||
|
||||
currentVersion := &ForgejoVersion{ID: 1}
|
||||
has, err := x.Get(currentVersion)
|
||||
inDBMigrationIDs, err := getInDBMigrationIDs(x)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get: %w", err)
|
||||
} else if !has {
|
||||
// If the version record does not exist we think
|
||||
// it is a fresh installation and we can skip all migrations.
|
||||
currentVersion.ID = 0
|
||||
currentVersion.Version = ExpectedVersion()
|
||||
|
||||
if _, err = x.InsertOne(currentVersion); err != nil {
|
||||
return fmt.Errorf("insert: %w", err)
|
||||
return err
|
||||
} else if len(inDBMigrationIDs) == 0 && freshDB {
|
||||
// During startup on a new, empty database, and during integration tests, we rely only on `SyncAllTables` to
|
||||
// create the DB schema. No migrations can be applied because `SyncAllTables` occurs later in the
|
||||
// initialization cycle. We mark all migrations as complete up to this point and only run future migrations.
|
||||
for _, migration := range orderedMigrations {
|
||||
err := recordMigrationComplete(x, migration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
inDBMigrationIDs, err = getInDBMigrationIDs(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if freshDB {
|
||||
return fmt.Errorf("unexpected state: migrator called with freshDB=true, but existing migrations in DB %#v", inDBMigrationIDs)
|
||||
}
|
||||
|
||||
v := currentVersion.Version
|
||||
|
||||
// Downgrading Forgejo's database version not supported
|
||||
if v > ExpectedVersion() {
|
||||
msg := fmt.Sprintf("Your Forgejo database (migration version: %d) is for a newer version of Forgejo, you cannot use the newer database for this old Forgejo release (%d).", v, ExpectedVersion())
|
||||
// invalidMigrations are those that are in the database, but aren't registered.
|
||||
invalidMigrations := inDBMigrationIDs.Difference(inMemoryMigrationIDs)
|
||||
if len(invalidMigrations) > 0 {
|
||||
// Downgrading Forgejo's database version not supported
|
||||
msg := fmt.Sprintf("Your Forgejo database has %d migration(s) (%s) for a newer version of Forgejo, you cannot use the newer database for this old Forgejo release.", len(invalidMigrations), strings.Join(invalidMigrations.Slice(), ", "))
|
||||
msg += "\nForgejo will exit to keep your database safe and unchanged. Please use the correct Forgejo release, do not change the migration version manually (incorrect manual operation may cause data loss)."
|
||||
if !setting.IsProd {
|
||||
msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE forgejo_version SET version=%d WHERE id=1;", ExpectedVersion())
|
||||
msg += "\nIf you are in development and know what you're doing, you can remove the migration records from the forgejo_migration table. The affect of those migrations will still be present."
|
||||
quoted := slices.Clone(invalidMigrations.Slice())
|
||||
for i, s := range quoted {
|
||||
quoted[i] = "'" + s + "'"
|
||||
}
|
||||
msg += fmt.Sprintf("\n DELETE FROM forgejo_migration WHERE id IN (%s)", strings.Join(quoted, ", "))
|
||||
}
|
||||
_, _ = fmt.Fprintln(os.Stderr, msg)
|
||||
log.Fatal(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some migration tasks depend on the git command
|
||||
if git.DefaultContext == nil {
|
||||
if err = git.InitSimple(context.Background()); err != nil {
|
||||
return err
|
||||
// unappliedMigrations are those that are registered but haven't been applied.
|
||||
unappliedMigrations := inMemoryMigrationIDs.Difference(inDBMigrationIDs)
|
||||
for _, migration := range orderedMigrations {
|
||||
if !unappliedMigrations.Contains(migration.id) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate
|
||||
for i, m := range migrations[v:] {
|
||||
log.Info("Migration[%d]: %s", v+int64(i), m.description)
|
||||
log.Info("Migration[%s]: %s", migration.id, migration.Description)
|
||||
|
||||
// Reset the mapper between each migration - migrations are not supposed to depend on each other
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err = m.migrate(x); err != nil {
|
||||
return fmt.Errorf("migration[%d]: %s failed: %w", v+int64(i), m.description, err)
|
||||
if err = migration.Upgrade(x); err != nil {
|
||||
return fmt.Errorf("migration[%s]: %s failed: %w", migration.id, migration.Description, err)
|
||||
}
|
||||
currentVersion.Version = v + int64(i) + 1
|
||||
if _, err = x.ID(1).Update(currentVersion); err != nil {
|
||||
|
||||
err := recordMigrationComplete(x, migration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := x.Sync(new(semver.ForgejoSemVer)); err != nil {
|
||||
return fmt.Errorf("sync: %w", err)
|
||||
}
|
||||
|
||||
return semver.SetVersionStringWithEngine(x, setting.ForgejoVersion)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,39 +1,314 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
migration_tests "forgejo.org/models/migrations/test"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// TestEnsureUpToDate tests the behavior of EnsureUpToDate.
|
||||
func TestEnsureUpToDate(t *testing.T) {
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoVersion))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
func noOpMigration(x *xorm.Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func nilMigration() *Migration {
|
||||
return &Migration{
|
||||
Description: "nothing",
|
||||
Upgrade: noOpMigration,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterMigration(t *testing.T) {
|
||||
resetMigrations()
|
||||
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v99b_neat_migration.go"
|
||||
})()
|
||||
|
||||
t.Run("migrationResolutionComplete", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&migrationResolutionComplete, true)()
|
||||
assert.PanicsWithValue(t, "attempted to register migration from some-path/v99b_neat_migration.go after migration resolution is already complete", func() {
|
||||
registerMigration(nilMigration())
|
||||
})
|
||||
})
|
||||
|
||||
for _, fn := range []string{
|
||||
"v99b_neat_migration.go", // no leading path
|
||||
"vb_neat_migration.go", // no version number
|
||||
"v12_neat_migration.go", // no migration group letter
|
||||
"v12a-neat-migration.go", // no undescore
|
||||
"v12a.go", // no descriptive identifier
|
||||
} {
|
||||
t.Run(fmt.Sprintf("bad name - %s", fn), func(t *testing.T) {
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return fn
|
||||
})()
|
||||
assert.PanicsWithValue(t, fmt.Sprintf("registerMigration must be invoked from a file matching migrationFilenameRegex, but was invoked from %q", fn), func() {
|
||||
registerMigration(nilMigration())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure error if there's no row in Forgejo Version.
|
||||
err := EnsureUpToDate(x)
|
||||
require.Error(t, err)
|
||||
|
||||
// Insert 'good' Forgejo Version row.
|
||||
_, err = x.InsertOne(&ForgejoVersion{ID: 1, Version: ExpectedVersion()})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = EnsureUpToDate(x)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Modify forgejo version to have a lower version.
|
||||
_, err = x.Exec("UPDATE `forgejo_version` SET version = ? WHERE id = 1", ExpectedVersion()-1)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = EnsureUpToDate(x)
|
||||
require.Error(t, err)
|
||||
registerMigration(nilMigration())
|
||||
found := false
|
||||
for _, m := range rawMigrations {
|
||||
if m.id == "v99b_neat_migration" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.True(t, found, "found registered migration")
|
||||
}
|
||||
|
||||
func TestResolveMigrations(t *testing.T) {
|
||||
t.Run("duplicate migration IDs", func(t *testing.T) {
|
||||
resetMigrations()
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v99b_neat_migration.go"
|
||||
})()
|
||||
registerMigration(nilMigration())
|
||||
registerMigration(nilMigration())
|
||||
|
||||
assert.PanicsWithValue(t, "migration id is duplicated: \"v99b_neat_migration\"", func() {
|
||||
resolveMigrations()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
resetMigrations()
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v99b_neat_migration.go"
|
||||
})()
|
||||
registerMigration(nilMigration())
|
||||
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v77a_neat_migration.go"
|
||||
})()
|
||||
registerMigration(nilMigration())
|
||||
|
||||
resolveMigrations()
|
||||
|
||||
assert.True(t, migrationResolutionComplete, "migration resolution complete")
|
||||
assert.Contains(t, inMemoryMigrationIDs, "v77a_neat_migration")
|
||||
assert.Contains(t, inMemoryMigrationIDs, "v99b_neat_migration")
|
||||
require.Len(t, orderedMigrations, 2)
|
||||
assert.Equal(t, "v77a_neat_migration", orderedMigrations[0].id)
|
||||
assert.Equal(t, "v99b_neat_migration", orderedMigrations[1].id)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetInDBMigrationIDs(t *testing.T) {
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration))
|
||||
defer deferable()
|
||||
require.NotNil(t, x)
|
||||
|
||||
migrationIDs, err := getInDBMigrationIDs(x)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, migrationIDs)
|
||||
assert.Empty(t, migrationIDs)
|
||||
|
||||
_, err = x.Insert(&ForgejoMigration{ID: "v77a_neat_migration"})
|
||||
require.NoError(t, err)
|
||||
_, err = x.Insert(&ForgejoMigration{ID: "v99b_neat_migration"})
|
||||
require.NoError(t, err)
|
||||
|
||||
migrationIDs, err = getInDBMigrationIDs(x)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, migrationIDs)
|
||||
assert.Len(t, migrationIDs, 2)
|
||||
assert.Contains(t, migrationIDs, "v77a_neat_migration")
|
||||
assert.Contains(t, migrationIDs, "v99b_neat_migration")
|
||||
}
|
||||
|
||||
func TestEnsureUpToDate(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
inMemory []string
|
||||
inDB []string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
desc: "up-to-date",
|
||||
inMemory: []string{"v77a_neat_migration"},
|
||||
inDB: []string{"v77a_neat_migration"},
|
||||
},
|
||||
{
|
||||
desc: "invalid-migration",
|
||||
inMemory: []string{},
|
||||
inDB: []string{"v77a_neat_migration"},
|
||||
err: "current Forgejo database has migration(s) v77a_neat_migration applied, which are not registered migrations",
|
||||
},
|
||||
{
|
||||
desc: "missing-migration",
|
||||
inMemory: []string{"v77a_neat_migration"},
|
||||
inDB: []string{},
|
||||
err: "current Forgejo database is missing migration(s) v77a_neat_migration",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
resetMigrations()
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration))
|
||||
defer deferable()
|
||||
require.NotNil(t, x)
|
||||
|
||||
for _, inMemory := range tc.inMemory {
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return fmt.Sprintf("some-path/%s.go", inMemory)
|
||||
})()
|
||||
registerMigration(nilMigration())
|
||||
}
|
||||
for _, inDB := range tc.inDB {
|
||||
x.Insert(&ForgejoMigration{ID: inDB})
|
||||
}
|
||||
|
||||
err := EnsureUpToDate(x)
|
||||
if tc.err == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrate(t *testing.T) {
|
||||
resetMigrations()
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration))
|
||||
defer deferable()
|
||||
require.NotNil(t, x)
|
||||
|
||||
v77aRun := false
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v77a_neat_migration.go"
|
||||
})()
|
||||
registerMigration(&Migration{
|
||||
Description: "nothing",
|
||||
Upgrade: func(x *xorm.Engine) error {
|
||||
v77aRun = true
|
||||
return nil
|
||||
},
|
||||
})
|
||||
// v77a_neat_migration will already be marked as already run
|
||||
_, err := x.Insert(&ForgejoMigration{ID: "v77a_neat_migration"})
|
||||
require.NoError(t, err)
|
||||
|
||||
v99bRun := false
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v99b_neat_migration.go"
|
||||
})()
|
||||
registerMigration(&Migration{
|
||||
Description: "nothing",
|
||||
Upgrade: func(x *xorm.Engine) error {
|
||||
v99bRun = true
|
||||
type ForgejoMagicFunctionality struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
}
|
||||
return x.Sync(new(ForgejoMagicFunctionality))
|
||||
},
|
||||
})
|
||||
|
||||
v99cRun := false
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v99c_neat_migration.go"
|
||||
})()
|
||||
registerMigration(&Migration{
|
||||
Description: "nothing",
|
||||
Upgrade: func(x *xorm.Engine) error {
|
||||
v99cRun = true
|
||||
type ForgejoMagicFunctionality struct {
|
||||
NewField string
|
||||
}
|
||||
return x.Sync(new(ForgejoMagicFunctionality))
|
||||
},
|
||||
})
|
||||
|
||||
err = Migrate(x, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, v77aRun, "v77aRun") // was already marked as run in the DB so shouldn't have run again
|
||||
assert.True(t, v99bRun, "v99bRun")
|
||||
assert.True(t, v99cRun, "v99cRun")
|
||||
migrationIDs, err := getInDBMigrationIDs(x)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, migrationIDs, "v77a_neat_migration")
|
||||
assert.Contains(t, migrationIDs, "v99b_neat_migration")
|
||||
assert.Contains(t, migrationIDs, "v99c_neat_migration")
|
||||
|
||||
// should be able to query all three of the fields from this table created, verifying both migrations creating the
|
||||
// table and adding a column were run
|
||||
rec := make([]map[string]any, 0)
|
||||
err = x.Cols("id", "name", "new_field").Table("forgejo_magic_functionality").Find(&rec)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestMigrateFreshDB(t *testing.T) {
|
||||
resetMigrations()
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration))
|
||||
defer deferable()
|
||||
require.NotNil(t, x)
|
||||
|
||||
v77aRun := false
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v77a_neat_migration.go"
|
||||
})()
|
||||
registerMigration(&Migration{
|
||||
Description: "nothing",
|
||||
Upgrade: func(x *xorm.Engine) error {
|
||||
v77aRun = true
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
v99bRun := false
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v99b_neat_migration.go"
|
||||
})()
|
||||
registerMigration(&Migration{
|
||||
Description: "nothing",
|
||||
Upgrade: func(x *xorm.Engine) error {
|
||||
v99bRun = true
|
||||
type ForgejoMagicFunctionality struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
}
|
||||
return x.Sync(new(ForgejoMagicFunctionality))
|
||||
},
|
||||
})
|
||||
|
||||
v99cRun := false
|
||||
defer test.MockVariableValue(&getMigrationFilename, func() string {
|
||||
return "some-path/v99c_neat_migration.go"
|
||||
})()
|
||||
registerMigration(&Migration{
|
||||
Description: "nothing",
|
||||
Upgrade: func(x *xorm.Engine) error {
|
||||
v99cRun = true
|
||||
type ForgejoMagicFunctionality struct {
|
||||
NewField string
|
||||
}
|
||||
return x.Sync(new(ForgejoMagicFunctionality))
|
||||
},
|
||||
})
|
||||
|
||||
err := Migrate(x, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, v77aRun, "v77aRun") // none should be run due to freshDB flag
|
||||
assert.False(t, v99bRun, "v99bRun")
|
||||
assert.False(t, v99cRun, "v99cRun")
|
||||
migrationIDs, err := getInDBMigrationIDs(x)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, migrationIDs, "v77a_neat_migration")
|
||||
assert.Contains(t, migrationIDs, "v99b_neat_migration")
|
||||
assert.Contains(t, migrationIDs, "v99c_neat_migration")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMigration(&Migration{
|
||||
Description: "add forgejo_migration table",
|
||||
Upgrade: addForeignKeysCollaboration,
|
||||
})
|
||||
}
|
||||
|
||||
func addForeignKeysCollaboration(x *xorm.Engine) error {
|
||||
type Collaboration struct {
|
||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(repository, id)"`
|
||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(user, id)"`
|
||||
}
|
||||
return syncDoctorForeignKey(x, []any{
|
||||
new(Collaboration),
|
||||
})
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMigration(&Migration{
|
||||
Description: "add forgejo_migration table",
|
||||
Upgrade: addForgejoMigration,
|
||||
})
|
||||
}
|
||||
|
||||
func addForgejoMigration(x *xorm.Engine) error {
|
||||
type ForgejoMigration struct {
|
||||
ID string `xorm:"pk"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
return x.Sync(new(ForgejoMigration))
|
||||
}
|
14
models/forgejo_migrations_legacy/main_test.go
Normal file
14
models/forgejo_migrations_legacy/main_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
migration_tests.MainTest(m)
|
||||
}
|
246
models/forgejo_migrations_legacy/migrate.go
Normal file
246
models/forgejo_migrations_legacy/migrate.go
Normal file
|
@ -0,0 +1,246 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"forgejo.org/models/forgejo/semver"
|
||||
"forgejo.org/models/forgejo_migrations"
|
||||
forgejo_v1_20 "forgejo.org/models/forgejo_migrations_legacy/v1_20"
|
||||
forgejo_v1_22 "forgejo.org/models/forgejo_migrations_legacy/v1_22"
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
// ForgejoVersion describes the Forgejo version table. Should have only one row with id = 1.
|
||||
type ForgejoVersion struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64
|
||||
}
|
||||
|
||||
type Migration struct {
|
||||
description string
|
||||
migrate func(*xorm.Engine) error
|
||||
}
|
||||
|
||||
// NewMigration creates a new migration.
|
||||
func NewMigration(desc string, fn func(*xorm.Engine) error) *Migration {
|
||||
return &Migration{desc, fn}
|
||||
}
|
||||
|
||||
// This is a sequence of additional Forgejo migrations.
|
||||
// Add new migrations to the bottom of the list.
|
||||
var migrations = []*Migration{
|
||||
// v0 -> v1
|
||||
NewMigration("Create the `forgejo_blocked_user` table", forgejo_v1_20.AddForgejoBlockedUser),
|
||||
// v1 -> v2
|
||||
NewMigration("Create the `forgejo_sem_ver` table", forgejo_v1_20.CreateSemVerTable),
|
||||
// v2 -> v3
|
||||
NewMigration("Create the `forgejo_auth_token` table", forgejo_v1_20.CreateAuthorizationTokenTable),
|
||||
// v3 -> v4
|
||||
NewMigration("Add the `default_permissions` column to the `repo_unit` table", forgejo_v1_22.AddDefaultPermissionsToRepoUnit),
|
||||
// v4 -> v5
|
||||
NewMigration("Create the `forgejo_repo_flag` table", forgejo_v1_22.CreateRepoFlagTable),
|
||||
// v5 -> v6
|
||||
NewMigration("Add the `wiki_branch` column to the `repository` table", forgejo_v1_22.AddWikiBranchToRepository),
|
||||
// v6 -> v7
|
||||
NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
|
||||
// v7 -> v8
|
||||
NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes),
|
||||
// v8 -> v9
|
||||
NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting),
|
||||
// v9 -> v10
|
||||
NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser),
|
||||
// v10 -> v11
|
||||
NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue),
|
||||
// v11 -> v12
|
||||
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount),
|
||||
// v12 -> v13
|
||||
NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease),
|
||||
// v13 -> v14
|
||||
NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge),
|
||||
// v14 -> v15
|
||||
NewMigration("Create the `federation_host` table", CreateFederationHostTable),
|
||||
// v15 -> v16
|
||||
NewMigration("Create the `federated_user` table", CreateFederatedUserTable),
|
||||
// v16 -> v17
|
||||
NewMigration("Add `normalized_federated_uri` column to `user` table", AddNormalizedFederatedURIToUser),
|
||||
// v17 -> v18
|
||||
NewMigration("Create the `following_repo` table", CreateFollowingRepoTable),
|
||||
// v18 -> v19
|
||||
NewMigration("Add external_url to attachment table", AddExternalURLColumnToAttachmentTable),
|
||||
// v19 -> v20
|
||||
NewMigration("Creating Quota-related tables", CreateQuotaTables),
|
||||
// v20 -> v21
|
||||
NewMigration("Add SSH keypair to `pull_mirror` table", AddSSHKeypairToPushMirror),
|
||||
// v21 -> v22
|
||||
NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential),
|
||||
// v22 -> v23
|
||||
NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge),
|
||||
// v23 -> v24
|
||||
NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken),
|
||||
// v24 -> v25
|
||||
NewMigration("Migrate `secret` column to store keying material", MigrateTwoFactorToKeying),
|
||||
// v25 -> v26
|
||||
NewMigration("Add `hash_blake2b` column to `package_blob` table", AddHashBlake2bToPackageBlob),
|
||||
// v26 -> v27
|
||||
NewMigration("Add `created_unix` column to `user_redirect` table", AddCreatedUnixToRedirect),
|
||||
// v27 -> v28
|
||||
NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser),
|
||||
// v28 -> v29
|
||||
NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation),
|
||||
// v29 -> v30
|
||||
NewMigration("Migrate `User.NormalizedFederatedURI` column to extract port & schema into FederatedHost", MigrateNormalizedFederatedURI),
|
||||
// v30 -> v31
|
||||
NewMigration("Normalize repository.topics to empty slice instead of null", SetTopicsAsEmptySlice),
|
||||
// v31 -> v32
|
||||
NewMigration("Migrate maven package name concatenation", ChangeMavenArtifactConcatenation),
|
||||
// v32 -> v33
|
||||
NewMigration("Add federated user activity tables, update the `federated_user` table & add indexes", FederatedUserActivityMigration),
|
||||
// v33 -> v34
|
||||
NewMigration("Add `notify-email` column to `action_run` table", AddNotifyEmailToActionRun),
|
||||
// v34 -> v35
|
||||
NewMigration("Noop because of https://codeberg.org/forgejo/forgejo/issues/8373", NoopAddIndexToActionRunStopped),
|
||||
// v35 -> v36
|
||||
NewMigration("Fix wiki unit default permission", FixWikiUnitDefaultPermission),
|
||||
// v36 -> v37
|
||||
NewMigration("Add `branch_filter` to `push_mirror` table", AddPushMirrorBranchFilter),
|
||||
// v37 -> v38
|
||||
NewMigration("Add `resolved_unix` column to `abuse_report` table", AddResolvedUnixToAbuseReport),
|
||||
// v38 -> v39
|
||||
NewMigration("Migrate `data` column of `secret` table to store keying material", MigrateActionSecretsToKeying),
|
||||
// v39 -> v40
|
||||
NewMigration("Add index for release sha1", AddIndexForReleaseSha1),
|
||||
// v40 -> v41
|
||||
NewMigration("Add foreign keys to stopwatch & tracked_time", AddForeignKeysStopwatchTrackedTime),
|
||||
// v41 -> v42
|
||||
NewMigration("Add action_run concurrency fields", AddActionRunConcurrency),
|
||||
// v42 -> v43
|
||||
NewMigration("Add action_run pre_execution_error field", AddActionRunPreExecutionError),
|
||||
// v43 -> v44
|
||||
NewMigration("Add foreign keys to access", AddForeignKeysAccess),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
func GetCurrentDBVersion(x *xorm.Engine) (int64, error) {
|
||||
if err := x.Sync(new(ForgejoVersion)); err != nil {
|
||||
return -1, fmt.Errorf("sync: %w", err)
|
||||
}
|
||||
|
||||
currentVersion := &ForgejoVersion{ID: 1}
|
||||
has, err := x.Get(currentVersion)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("get: %w", err)
|
||||
}
|
||||
if !has {
|
||||
return -1, nil
|
||||
}
|
||||
return currentVersion.Version, nil
|
||||
}
|
||||
|
||||
// ExpectedVersion returns the expected Forgejo database version.
|
||||
func ExpectedVersion() int64 {
|
||||
return int64(len(migrations))
|
||||
}
|
||||
|
||||
// EnsureUpToDate will check if the Forgejo database is at the correct version.
|
||||
func EnsureUpToDate(x *xorm.Engine) error {
|
||||
currentDB, err := GetCurrentDBVersion(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentDB < 0 {
|
||||
return errors.New("database has not been initialized")
|
||||
}
|
||||
|
||||
expected := ExpectedVersion()
|
||||
|
||||
if currentDB != expected {
|
||||
return fmt.Errorf(`current Forgejo database version %d is not equal to the expected version %d. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expected)
|
||||
}
|
||||
|
||||
return forgejoMigrationsEnsureUpToDate(x)
|
||||
}
|
||||
|
||||
var forgejoMigrationsEnsureUpToDate = forgejo_migrations.EnsureUpToDate
|
||||
|
||||
// Migrate Forgejo database to current version.
|
||||
func Migrate(x *xorm.Engine) error {
|
||||
// Set a new clean the default mapper to GonicMapper as that is the default for .
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync(new(ForgejoVersion)); err != nil {
|
||||
return fmt.Errorf("sync: %w", err)
|
||||
}
|
||||
|
||||
currentVersion := &ForgejoVersion{ID: 1}
|
||||
has, err := x.Get(currentVersion)
|
||||
freshDB := false
|
||||
if err != nil {
|
||||
return fmt.Errorf("get: %w", err)
|
||||
} else if !has {
|
||||
// If the version record does not exist we think
|
||||
// it is a fresh installation and we can skip all migrations.
|
||||
currentVersion.ID = 0
|
||||
currentVersion.Version = ExpectedVersion()
|
||||
freshDB = true
|
||||
|
||||
if _, err = x.InsertOne(currentVersion); err != nil {
|
||||
return fmt.Errorf("insert: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
v := currentVersion.Version
|
||||
|
||||
// Downgrading Forgejo's database version not supported
|
||||
if v > ExpectedVersion() {
|
||||
msg := fmt.Sprintf("Your Forgejo database (migration version: %d) is for a newer version of Forgejo, you cannot use the newer database for this old Forgejo release (%d).", v, ExpectedVersion())
|
||||
msg += "\nForgejo will exit to keep your database safe and unchanged. Please use the correct Forgejo release, do not change the migration version manually (incorrect manual operation may cause data loss)."
|
||||
if !setting.IsProd {
|
||||
msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE forgejo_version SET version=%d WHERE id=1;", ExpectedVersion())
|
||||
}
|
||||
_, _ = fmt.Fprintln(os.Stderr, msg)
|
||||
log.Fatal(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some migration tasks depend on the git command
|
||||
if git.DefaultContext == nil {
|
||||
if err = git.InitSimple(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate
|
||||
for i, m := range migrations[v:] {
|
||||
log.Info("Migration[%d]: %s", v+int64(i), m.description)
|
||||
// Reset the mapper between each migration - migrations are not supposed to depend on each other
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err = m.migrate(x); err != nil {
|
||||
return fmt.Errorf("migration[%d]: %s failed: %w", v+int64(i), m.description, err)
|
||||
}
|
||||
currentVersion.Version = v + int64(i) + 1
|
||||
if _, err = x.ID(1).Update(currentVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := x.Sync(new(semver.ForgejoSemVer)); err != nil {
|
||||
return fmt.Errorf("sync: %w", err)
|
||||
}
|
||||
|
||||
if err := semver.SetVersionStringWithEngine(x, setting.ForgejoVersion); err != nil {
|
||||
return fmt.Errorf("SetVersionStringWithEngine: %w", err)
|
||||
}
|
||||
|
||||
return forgejo_migrations.Migrate(x, freshDB)
|
||||
}
|
45
models/forgejo_migrations_legacy/migrate_test.go
Normal file
45
models/forgejo_migrations_legacy/migrate_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// TestEnsureUpToDate tests the behavior of EnsureUpToDate.
|
||||
func TestEnsureUpToDate(t *testing.T) {
|
||||
defer test.MockVariableValue(&forgejoMigrationsEnsureUpToDate, func(x *xorm.Engine) error {
|
||||
return nil
|
||||
})()
|
||||
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoVersion))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure error if there's no row in Forgejo Version.
|
||||
err := EnsureUpToDate(x)
|
||||
require.Error(t, err)
|
||||
|
||||
// Insert 'good' Forgejo Version row.
|
||||
_, err = x.InsertOne(&ForgejoVersion{ID: 1, Version: ExpectedVersion()})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = EnsureUpToDate(x)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Modify forgejo version to have a lower version.
|
||||
_, err = x.Exec("UPDATE `forgejo_version` SET version = ? WHERE id = 1", ExpectedVersion()-1)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = EnsureUpToDate(x)
|
||||
require.Error(t, err)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"forgejo.org/models/migrations/base"
|
||||
"forgejo.org/models/gitea_migrations/base"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"time"
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
|
@ -6,7 +6,7 @@ package v1_22
|
|||
import (
|
||||
"testing"
|
||||
|
||||
migration_tests "forgejo.org/models/migrations/test"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue