Merge branch 'mirror' into fgj

This commit is contained in:
Minecon724 2025-10-17 09:07:44 +02:00
commit 8c555a97d8
Signed by untrusted user who does not match committer: m724
GPG key ID: A02E6E67AB961189
769 changed files with 5807 additions and 2287 deletions

View file

@ -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",

View file

@ -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

View file

@ -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": {

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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'

View file

@ -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 }}

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -28,7 +28,7 @@ jobs:
runs-on: docker
container:
image: data.forgejo.org/renovate/renovate:41.131.9
image: data.forgejo.org/renovate/renovate:41.146.0
steps:
- name: Load renovate repo cache

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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.131.9 # 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
@ -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

File diff suppressed because one or more lines are too long

View file

@ -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() {

View file

@ -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{

View file

@ -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()

View file

@ -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{

View file

@ -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)
},

View file

@ -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:]...)...),
}

View file

@ -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:]...)...),
}

View file

@ -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()

View file

@ -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{

View file

@ -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{

View file

@ -40,6 +40,7 @@ func microcmdUserDelete() *cli.Command {
Usage: "Purge user, all their repositories, organizations and comments",
},
},
Before: noDanglingArgs,
Action: runDeleteUser,
}
}

View file

@ -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,
}
}

View file

@ -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{

View file

@ -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{

View file

@ -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{

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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,
}
}

View file

@ -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{

View file

@ -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{

View file

@ -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,
}
}

View file

@ -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{

View file

@ -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{

View file

@ -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,
},
},

View file

@ -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

View file

@ -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

View file

@ -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{

View file

@ -40,7 +40,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{

View file

@ -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

View file

@ -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`.
@ -1706,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
;;
@ -1762,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 }}
;;
@ -1923,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
@ -2041,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
@ -2081,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
@ -2102,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
@ -2120,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
@ -2275,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]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -2348,7 +2348,7 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Check for new Gitea versions
;; Check for new Forgejo versions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[cron.update_checker]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -2449,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 +2700,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

View file

@ -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],

59
go.mod
View file

@ -2,7 +2,7 @@ module forgejo.org
go 1.24.0
toolchain go1.24.7
toolchain go1.25.3
require (
code.forgejo.org/f3/gof3/v3 v3.11.1
@ -17,8 +17,10 @@ require (
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
@ -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
@ -99,13 +102,13 @@ require (
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
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
@ -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
@ -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
)

147
go.sum
View file

@ -46,10 +46,16 @@ code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zC
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=
@ -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=
@ -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=
@ -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=

View file

@ -218,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
}

View file

@ -6,7 +6,11 @@ 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) {
@ -36,3 +40,49 @@ func TestSetDefaultConcurrencyGroup(t *testing.T) {
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)
})
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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)

View file

@ -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
}

View file

@ -9,41 +9,101 @@ import (
"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 []*schemas.Table) ([]*schemas.Table, error) {
func calculateTableForeignKeyOrder(tables []schemaWithDefaultBean) ([]schemaWithDefaultBean, error) {
remainingTables := slices.Clone(tables)
// Create a lookup for each table that has a foreign key, and a slice of the tables that it references it.
referencingTables := make(map[string][]string)
for _, table := range remainingTables {
tableName := table.Name
for _, fk := range table.ForeignKeys {
referencingTables[tableName] = append(referencingTables[tableName], fk.TargetTableName)
}
}
orderedTables := make([]*schemas.Table, 0, len(remainingTables))
referencingTables := calculateReferencingTables(remainingTables)
orderedTables := make([]schemaWithDefaultBean, 0, len(remainingTables))
for len(remainingTables) > 0 {
nextGroup := make([]*schemas.Table, 0, len(remainingTables))
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.Name]
if ok && len(slice) > 1 { // This table is still referencing an uncreated table
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
@ -59,12 +119,12 @@ func calculateTableForeignKeyOrder(tables []*schemas.Table) ([]*schemas.Table, e
// 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 *schemas.Table) bool {
return remainingTable.Name == doneTable.Name
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.Name
return tableName == doneTable.schema.Name
})
}
}
@ -74,14 +134,10 @@ func calculateTableForeignKeyOrder(tables []*schemas.Table) ([]*schemas.Table, e
}
// Create a list of registered database tables in their "foreign key order", per calculateTableForeignKeyOrder.
func foreignKeyOrderedTables() ([]*schemas.Table, error) {
schemaTables := make([]*schemas.Table, 0, len(tables))
for _, tbl := range tables {
table, err := TableInfo(tbl)
if err != nil {
return nil, fmt.Errorf("foreignKeyOrderedTables: failure to fetch schema table for bean %#v: %w", tbl, err)
}
schemaTables = append(schemaTables, table)
func foreignKeyOrderedTables() ([]schemaWithDefaultBean, error) {
schemaTables, err := cachedSchemaTables()
if err != nil {
return nil, err
}
orderedTables, err := calculateTableForeignKeyOrder(schemaTables)
@ -102,7 +158,7 @@ func tableNameLookupOrder() (map[string]int, error) {
lookupMap := make(map[string]int, len(tables))
for i, table := range tables {
lookupMap[table.Name] = i
lookupMap[table.schema.Name] = i
}
return lookupMap, nil
@ -117,7 +173,7 @@ func TableNameInsertionOrderSortFunc(table1, table2 string) int {
}
// 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/migrations/test.
// 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
@ -129,3 +185,91 @@ func TableNameInsertionOrderSortFunc(table1, table2 string) int {
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
}

View file

@ -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()
}

View file

@ -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)
}

View file

@ -1921,4 +1921,3 @@
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
topics: '[]'

View 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';
```

View file

@ -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) {

View file

@ -1,235 +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),
// 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),
}
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
}

View file

@ -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")
}

View file

@ -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))
}

View 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)
}

View 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)
}

View 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)
}

View file

@ -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"

View file

@ -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"
)

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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) {

View file

@ -6,7 +6,7 @@ package v1_22
import (
"testing"
migration_tests "forgejo.org/models/migrations/test"
migration_tests "forgejo.org/models/gitea_migrations/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -1,7 +1,7 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations
package forgejo_migrations_legacy
import (
"context"
@ -23,7 +23,7 @@ func MigrateTwoFactorToKeying(x *xorm.Engine) error {
var err error
// When upgrading from Forgejo v9 to v10, this migration will already be
// called from models/migrations/migrations.go migration 304 and must not
// called from models/gitea_migrations/migrations.go migration 304 and must not
// be run twice.
var version int
_, err = x.Table("version").Where("`id` = 1").Select("version").Get(&version)

View file

@ -1,13 +1,13 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations
package forgejo_migrations_legacy
import (
"testing"
"forgejo.org/models/auth"
migration_tests "forgejo.org/models/migrations/test"
migration_tests "forgejo.org/models/gitea_migrations/test"
"forgejo.org/modules/keying"
"forgejo.org/modules/timeutil"

View file

@ -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"

View file

@ -1,7 +1,7 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations
package forgejo_migrations_legacy
import (
"forgejo.org/modules/timeutil"

View file

@ -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"

View file

@ -1,7 +1,7 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations
package forgejo_migrations_legacy
import (
"database/sql"

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