1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2025-09-15 18:56:59 +00:00

Merge branch 'forgejo' into timeout-config-to-remove-resolved-reports

This commit is contained in:
Leni Kadali 2025-06-25 12:09:38 +02:00
commit 7d03d7dd1e
632 changed files with 19250 additions and 6456 deletions

View file

@ -13,6 +13,13 @@ forgejo.org/models
IsErrSHANotFound
IsErrMergeDivergingFastForwardOnly
forgejo.org/models/activities
GetActivityByID
NewFederatedUserActivity
CreateUserActivity
GetFollowingFeeds
FederatedUserActivity.loadActor
forgejo.org/models/auth
WebAuthnCredentials
@ -54,9 +61,17 @@ forgejo.org/models/user
IsErrExternalLoginUserAlreadyExist
IsErrExternalLoginUserNotExist
NewFederatedUser
NewFederatedUserFollower
IsErrUserSettingIsNotExist
GetUserAllSettings
DeleteUserSetting
GetFederatedUser
GetFederatedUserByUserID
UpdateFederatedUser
GetFollowersForUser
AddFollower
RemoveFollower
IsFollowingAp
forgejo.org/modules/activitypub
NewContext
@ -87,12 +102,24 @@ forgejo.org/modules/eventsource
Event.String
forgejo.org/modules/forgefed
NewForgeFollowFromAp
NewForgeFollow
ForgeFollow.MarshalJSON
ForgeFollow.UnmarshalJSON
ForgeFollow.Validate
NewForgeUndoLike
ForgeUndoLike.UnmarshalJSON
ForgeUndoLike.Validate
NewForgeUserActivityFromAp
NewForgeUserActivity
ForgeUserActivity.Validate
NewPersonIDFromModel
GetItemByType
JSONUnmarshalerFn
NotEmpty
NewForgeUserActivityNoteFromAp
newNote
ForgeUserActivityNote.Validate
ToRepository
OnRepository
@ -204,6 +231,7 @@ forgejo.org/modules/util/filebuffer
forgejo.org/modules/validation
IsErrNotValid
ValidateIDExists
forgejo.org/modules/web
RouteMock

View file

@ -6,7 +6,7 @@
"ghcr.io/devcontainers/features/node:1": {
"version": "22"
},
"ghcr.io/devcontainers/features/git-lfs:1.2.3": {},
"ghcr.io/devcontainers/features/git-lfs:1.2.4": {},
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
},
"customizations": {

View file

@ -37,13 +37,9 @@ coverage.all
coverage/
cpu.out
/modules/migration/bindata.go
/modules/migration/bindata.go.hash
/modules/options/bindata.go
/modules/options/bindata.go.hash
/modules/public/bindata.go
/modules/public/bindata.go.hash
/modules/templates/bindata.go
/modules/templates/bindata.go.hash
*.db

View file

@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
**NOTE: If your issue is a security concern, please email <security@forgejo.org> (GPG: `A4676E79`) instead of opening a public issue.**
**NOTE: If your issue is a security concern, please email <security@forgejo.org> ([security.txt](https://forgejo.org/.well-known/security.txt)) instead of opening a public issue.**
- type: markdown
attributes:
value: |

View file

@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
**NOTE: If your issue is a security concern, please email <security@forgejo.org> (GPG: `A4676E79`) instead of opening a public issue.**
**NOTE: If your issue is a security concern, please email <security@forgejo.org> ([security.txt](https://forgejo.org/.well-known/security.txt)) instead of opening a public issue.**
- type: markdown
attributes:
value: |

View file

@ -1,4 +1,4 @@
FROM data.forgejo.org/oci/alpine:3.21
FROM data.forgejo.org/oci/alpine:3.22
ARG RELEASE_VERSION=unkown
LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.version="${RELEASE_VERSION}"

View file

@ -18,7 +18,7 @@ runs:
- name: install packages
run: |
apt-get update -qq
apt-get -q install -qq -y ${PACKAGES}
apt-get -q install --allow-downgrades -qq -y ${PACKAGES}
env:
PACKAGES: ${{inputs.packages}}
- name: remove temporary package list to prevent using it in other steps

View file

@ -164,7 +164,7 @@ jobs:
- name: build container & release
if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5
with:
forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
@ -183,7 +183,7 @@ jobs:
- name: build rootless container
if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5
with:
forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"

View file

@ -44,7 +44,7 @@ jobs:
- uses: https://data.forgejo.org/actions/checkout@v4
- name: copy & sign
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.4
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.5
with:
from-forgejo: ${{ vars.FORGEJO }}
to-forgejo: ${{ vars.FORGEJO }}

View file

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

View file

@ -0,0 +1,71 @@
#
# Additional integration tests designed to run once a day when
# `mirror.yml` pushes to https://codeberg.org/forgejo-integration/forgejo
# and send a notification via email should they fail.
#
# For debug purposes:
#
# - uncomment [on].pull_request
# - swap 'forgejo-integration' and 'forgejo-coding'
# - open a pull request at https://codeberg.org/forgejo/forgejo and fix things
# - swap 'forgejo-integration' and 'forgejo-coding'
# - comment [on].pull_request
#
name: testing-integration
on:
# pull_request:
push:
tags: 'v[0-9]+.[0-9]+.*'
branches:
- 'forgejo'
- 'v*/forgejo'
jobs:
test-unit:
# if: vars.ROLE == 'forgejo-coding'
if: vars.ROLE == 'forgejo-integration'
runs-on: docker
container:
image: 'data.forgejo.org/oci/node:22-bookworm'
options: --tmpfs /tmp:exec,noatime
steps:
- uses: https://data.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows-composite/setup-env
- name: install git 2.30
uses: ./.forgejo/workflows-composite/apt-install-from
with:
packages: git/bullseye git-lfs/bullseye
release: bullseye
- uses: ./.forgejo/workflows-composite/build-backend
- run: |
su forgejo -c 'make test-backend test-check'
timeout-minutes: 120
env:
RACE_ENABLED: 'true'
TAGS: bindata
test-sqlite:
# if: vars.ROLE == 'forgejo-coding'
if: vars.ROLE == 'forgejo-integration'
runs-on: docker
container:
image: 'data.forgejo.org/oci/node:22-bookworm'
options: --tmpfs /tmp:exec,noatime
steps:
- uses: https://data.forgejo.org/actions/checkout@v4
- uses: ./.forgejo/workflows-composite/setup-env
- name: install git 2.30
uses: ./.forgejo/workflows-composite/apt-install-from
with:
packages: git/bullseye git-lfs/bullseye
release: bullseye
- uses: ./.forgejo/workflows-composite/build-backend
- run: |
su forgejo -c 'make test-sqlite-migration test-sqlite'
timeout-minutes: 120
env:
TAGS: sqlite sqlite_unlock_notify
RACE_ENABLED: true
TEST_TAGS: sqlite sqlite_unlock_notify
USE_REPO_TEST_DIR: 1

View file

@ -91,6 +91,7 @@ jobs:
RACE_ENABLED: 'true'
TAGS: bindata
TEST_ELASTICSEARCH_URL: http://elasticsearch:9200
TEST_MINIO_ENDPOINT: minio:9000
test-e2e:
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
runs-on: docker
@ -114,6 +115,11 @@ jobs:
run: |
su forgejo -c 'make deps-frontend frontend'
- uses: ./.forgejo/workflows-composite/build-backend
- name: Decide to run all tests
id: run-all
if: contains(github.event.pull_request.labels.*.name, 'run-all-playwright-tests') || contains(github.event.pull_request.title, 'playwright')
run: |
echo "all=1" >> "$GITHUB_OUTPUT"
- name: Get changed files
id: changed-files
uses: https://data.forgejo.org/tj-actions/changed-files@v46
@ -126,6 +132,7 @@ jobs:
USE_REPO_TEST_DIR: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
CHANGED_FILES: ${{steps.changed-files.outputs.all_changed_files}}
RUN_ALL: ${{steps.run-all.all}}
- name: Upload test artifacts on failure
if: failure()
uses: https://data.forgejo.org/forgejo/upload-artifact@v4

View file

@ -10,6 +10,7 @@
# Javascript and CSS code.
web_src/.* @beowulf @gusted
web_src/css/.* @0ko
# HTML templates used by the backend.
templates/.* @beowulf @gusted

View file

@ -1,6 +1,6 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/forgejo.org
WORKDIR ${GOPATH}/src/forgejo.org
RUN make clean
RUN make clean-no-bindata
RUN make frontend
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
RUN LDFLAGS="-buildid=" make RELEASE_VERSION=$RELEASE_VERSION GOFLAGS="-trimpath" go-check generate-backend static-executable && xx-verify gitea
RUN LDFLAGS="-buildid=" make FORGEJO_GENERATE_SKIP_HASH=true RELEASE_VERSION=$RELEASE_VERSION GOFLAGS="-trimpath" go-check generate-backend static-executable && xx-verify gitea
# Copy local files
COPY docker/root /tmp/local
@ -51,7 +51,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/go/src/forgejo.org/environment-to-ini
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
FROM data.forgejo.org/oci/alpine:3.21
FROM data.forgejo.org/oci/alpine:3.22
ARG RELEASE_VERSION
LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \

View file

@ -1,6 +1,6 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm
COPY . ${GOPATH}/src/forgejo.org
WORKDIR ${GOPATH}/src/forgejo.org
RUN make clean
RUN make clean-no-bindata
RUN make frontend
RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
RUN make FORGEJO_GENERATE_SKIP_HASH=true RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea
# Copy local files
COPY docker/rootless /tmp/local
@ -49,7 +49,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/go/src/forgejo.org/environment-to-ini
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
FROM data.forgejo.org/oci/alpine:3.21
FROM data.forgejo.org/oci/alpine:3.22
ARG RELEASE_VERSION
LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \

View file

@ -37,19 +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.2.1 # 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.1.6 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # 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.32.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.1 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@40.11.19 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@41.1.4 # 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: ...
@ -130,7 +128,7 @@ WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_DEST := modules/migration/bindata.go modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go
@ -223,7 +221,6 @@ help:
@echo " - lint-go lint go files"
@echo " - lint-go-fix lint go files and fix issues"
@echo " - lint-go-vet lint go files with vet"
@echo " - lint-go-gopls lint go files with gopls"
@echo " - lint-js lint js files"
@echo " - lint-js-fix lint js files and fix issues"
@echo " - lint-css lint css files"
@ -326,8 +323,12 @@ clean-all: clean
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
.PHONY: clean
clean:
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
clean: clean-no-bindata
rm -rf $(BINDATA_DEST) $(BINDATA_HASH)
.PHONY: clean-no-bindata
clean-no-bindata:
rm -rf $(EXECUTABLE) $(DIST) \
integrations*.test \
e2e*.test \
tests/integration/gitea-integration-* \
@ -484,11 +485,6 @@ lint-go-vet:
@echo "Running go vet..."
@$(GO) vet ./...
.PHONY: lint-go-gopls
lint-go-gopls:
@echo "Running gopls check..."
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA)
.PHONY: lint-editorconfig
lint-editorconfig:
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows
@ -557,7 +553,7 @@ test-check:
.PHONY: test\#%
test\#%:
@echo "Running go test with -tags '$(TEST_TAGS)'..."
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
.PHONY: coverage
@ -924,13 +920,11 @@ deps-tools:
$(GO) install $(GOFUMPT_PACKAGE)
$(GO) install $(GOLANGCI_LINT_PACKAGE)
$(GO) install $(GXZ_PACKAGE)
$(GO) install $(MISSPELL_PACKAGE)
$(GO) install $(SWAGGER_PACKAGE)
$(GO) install $(XGO_PACKAGE)
$(GO) install $(GO_LICENSES_PACKAGE)
$(GO) install $(GOVULNCHECK_PACKAGE)
$(GO) install $(GOMOCK_PACKAGE)
$(GO) install $(GOPLS_PACKAGE)
node_modules: package-lock.json
npm install --no-save

File diff suppressed because one or more lines are too long

View file

@ -1,14 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build vendor
package main
// Libraries that are included to vendor utilities used during build.
// These libraries will not be included in a normal compilation.
import (
// for embed
_ "github.com/shurcooL/vfsgen"
)

View file

@ -1,5 +1,6 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
//go:build ignore
@ -7,30 +8,40 @@ package main
import (
"bytes"
"crypto/sha1"
"crypto/sha256"
"fmt"
"io"
"io/fs"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"text/template"
"github.com/shurcooL/vfsgen"
"github.com/klauspost/compress/zstd"
)
func needsUpdate(dir, filename string) (bool, []byte) {
needRegen := false
func fileExists(filename string) bool {
_, err := os.Stat(filename)
if err != nil {
needRegen = true
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
panic(err)
}
func needsUpdate(dir, filename string) (bool, []byte) {
needRegen := !fileExists(filename)
oldHash, err := os.ReadFile(filename + ".hash")
if err != nil {
oldHash = []byte{}
}
hasher := sha1.New()
hasher := sha256.New()
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
@ -51,7 +62,7 @@ func needsUpdate(dir, filename string) (bool, []byte) {
newHash := hasher.Sum([]byte{})
if bytes.Compare(oldHash, newHash) != 0 {
if !bytes.Equal(oldHash, newHash) {
return true, newHash
}
@ -69,24 +80,280 @@ func main() {
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
}
update, newHash := needsUpdate(dir, filename)
if os.Getenv("FORGEJO_GENERATE_SKIP_HASH") == "true" && fileExists(filename) {
fmt.Printf("bindata %s already exists and FORGEJO_GENERATE_SKIP_HASH=true\n", packageName)
return
}
update, newHash := needsUpdate(dir, filename)
if !update {
fmt.Printf("bindata for %s already up-to-date\n", packageName)
fmt.Printf("bindata %s already exists and the checksum is a match\n", packageName)
return
}
fmt.Printf("generating bindata for %s\n", packageName)
var fsTemplates http.FileSystem = http.Dir(dir)
err := vfsgen.Generate(fsTemplates, vfsgen.Options{
PackageName: packageName,
BuildTags: "bindata",
VariableName: "Assets",
Filename: filename,
UseGlobalModTime: useGlobalModTime,
})
root, err := os.OpenRoot(dir)
if err != nil {
log.Fatalf("%v\n", err)
log.Fatal(err)
}
out, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil {
log.Fatal(err)
}
defer out.Close()
if err := generate(root.FS(), packageName, useGlobalModTime, out); err != nil {
log.Fatal(err)
}
_ = os.WriteFile(filename+".hash", newHash, 0o666)
}
type file struct {
Path string
Name string
UncompressedSize int
CompressedData []byte
UncompressedData []byte
}
type direntry struct {
Name string
IsDir bool
}
func generate(fsRoot fs.FS, packageName string, globalTime bool, output io.Writer) error {
enc, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
if err != nil {
return err
}
files := []file{}
dirs := map[string][]direntry{}
if err := fs.WalkDir(fsRoot, ".", func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
entries, err := fs.ReadDir(fsRoot, filePath)
if err != nil {
return err
}
dirEntries := make([]direntry, 0, len(entries))
for _, entry := range entries {
dirEntries = append(dirEntries, direntry{Name: entry.Name(), IsDir: entry.IsDir()})
}
dirs[filePath] = dirEntries
return nil
}
src, err := fs.ReadFile(fsRoot, filePath)
if err != nil {
return err
}
dst := enc.EncodeAll(src, nil)
if len(dst) < len(src) {
files = append(files, file{
Path: filePath,
Name: path.Base(filePath),
UncompressedSize: len(src),
CompressedData: dst,
})
} else {
files = append(files, file{
Path: filePath,
Name: path.Base(filePath),
UncompressedData: src,
})
}
return nil
}); err != nil {
return err
}
return generatedTmpl.Execute(output, map[string]any{
"Packagename": packageName,
"GlobalTime": globalTime,
"Files": files,
"Dirs": dirs,
})
}
var generatedTmpl = template.Must(template.New("").Parse(`// Code generated by efs-gen. DO NOT EDIT.
//go:build bindata
package {{.Packagename}}
import (
"bytes"
"time"
"io"
"io/fs"
"github.com/klauspost/compress/zstd"
)
type normalFile struct {
name string
content []byte
}
type compressedFile struct {
name string
uncompressedSize int64
data []byte
}
var files = map[string]any{
{{- range .Files}}
"{{.Path}}": {{if .CompressedData}}compressedFile{"{{.Name}}", {{.UncompressedSize}}, []byte({{printf "%+q" .CompressedData}})}{{else}}normalFile{"{{.Name}}", []byte({{printf "%+q" .UncompressedData}})}{{end}},
{{- end}}
}
var dirs = map[string][]fs.DirEntry{
{{- range $key, $entry := .Dirs}}
"{{$key}}": {
{{- range $entry}}
direntry{"{{.Name}}", {{.IsDir}}},
{{- end}}
},
{{- end}}
}
type assets struct{}
var Assets = assets{}
func (a assets) Open(name string) (fs.File, error) {
f, ok := files[name]
if !ok {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}
switch f := f.(type) {
case normalFile:
return file{name: f.name, size: int64(len(f.content)), data: bytes.NewReader(f.content)}, nil
case compressedFile:
r, _ := zstd.NewReader(bytes.NewReader(f.data))
return &compressFile{name: f.name, size: f.uncompressedSize, data: r, content: f.data}, nil
default:
panic("unknown file type")
}
}
func (a assets) ReadDir(name string) ([]fs.DirEntry, error) {
d, ok := dirs[name]
if !ok {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}
return d, nil
}
type file struct {
name string
size int64
data io.ReadSeeker
}
var _ io.ReadSeeker = (*file)(nil)
func (f file) Stat() (fs.FileInfo, error) {
return fileinfo{name: f.name, size: f.size}, nil
}
func (f file) Read(p []byte) (int, error) {
return f.data.Read(p)
}
func (f file) Seek(offset int64, whence int) (int64, error) {
return f.data.Seek(offset, whence)
}
func (f file) Close() error { return nil }
type compressFile struct {
name string
size int64
data *zstd.Decoder
content []byte
zstdPos int64
seekPos int64
}
var _ io.ReadSeeker = (*compressFile)(nil)
func (f *compressFile) Stat() (fs.FileInfo, error) {
return fileinfo{name: f.name, size: f.size}, nil
}
func (f *compressFile) Read(p []byte) (int, error) {
if f.zstdPos > f.seekPos {
if err := f.data.Reset(bytes.NewReader(f.content)); err != nil {
return 0, err
}
f.zstdPos = 0
}
if f.zstdPos < f.seekPos {
if _, err := io.CopyN(io.Discard, f.data, f.seekPos - f.zstdPos); err != nil {
return 0, err
}
f.zstdPos = f.seekPos
}
n, err := f.data.Read(p)
f.zstdPos += int64(n)
f.seekPos = f.zstdPos
return n, err
}
func (f *compressFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
f.seekPos = 0 + offset
case io.SeekCurrent:
f.seekPos += offset
case io.SeekEnd:
f.seekPos = f.size + offset
}
return f.seekPos, nil
}
func (f *compressFile) Close() error {
f.data.Close()
return nil
}
func (f *compressFile) ZstdBytes() []byte { return f.content }
type fileinfo struct {
name string
size int64
}
func (f fileinfo) Name() string { return f.name }
func (f fileinfo) Size() int64 { return f.size }
func (f fileinfo) Mode() fs.FileMode { return 0o444 }
func (f fileinfo) ModTime() time.Time { return {{if .GlobalTime}}GlobalModTime(f.name){{else}}time.Unix(0, 0){{end}} }
func (f fileinfo) IsDir() bool { return false }
func (f fileinfo) Sys() any { return nil }
type direntry struct {
name string
isDir bool
}
func (d direntry) Name() string { return d.name }
func (d direntry) IsDir() bool { return d.isDir }
func (d direntry) Type() fs.FileMode {
if d.isDir {
return 0o755 | fs.ModeDir
}
return 0o444
}
func (direntry) Info() (fs.FileInfo, error) { return nil, fs.ErrNotExist }
`))

View file

@ -52,7 +52,7 @@ func initBlueMondayPolicy() {
policy.AllowAttrs("id").Matching(positionalPlaceholderRe).OnElements("code")
// Allowed elements with no attributes. Must be a recognized tagname.
policy.AllowElements("strong", "br", "b", "strike", "code", "i")
policy.AllowElements("strong", "br", "b", "strike", "code", "i", "kbd")
// TODO: Remove <c> in `actions.workflow.dispatch.trigger_found`.
policy.AllowNoAttrs().OnElements("c")

View file

@ -37,6 +37,7 @@ func TestLocalizationPolicy(t *testing.T) {
assert.Empty(t, checkLocaleContent([]byte("teams.specific_repositories_helper = Members will only have access to repositories explicitly added to the team. Selecting this <strong>will not</strong> automatically remove repositories already added with <i>All repositories</i>.")))
assert.Empty(t, checkLocaleContent([]byte("sqlite_helper = File path for the SQLite3 database.<br>Enter an absolute path if you run Forgejo as a service.")))
assert.Empty(t, checkLocaleContent([]byte("hi_user_x = Hi <b>%s</b>,")))
assert.Empty(t, checkLocaleContent([]byte("key = Press <kbd>Shift</kbd>")))
assert.Equal(t, []string{"error404: The page you are trying to reach either <strong\x1b[31m title='aaa'\x1b[0m>does not exist</strong> or <strong>you are not authorized</strong> to view it."}, checkLocaleContent([]byte("error404 = The page you are trying to reach either <strong title='aaa'>does not exist</strong> or <strong>you are not authorized</strong> to view it.")))
})

View file

@ -4,25 +4,28 @@
package cmd
import (
"context"
"fmt"
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdActions represents the available actions sub-commands.
CmdActions = &cli.Command{
// CmdActions represents the available actions sub-commands.
func cmdActions() *cli.Command {
return &cli.Command{
Name: "actions",
Usage: "Manage Forgejo Actions",
Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken,
Commands: []*cli.Command{
subcmdActionsGenRunnerToken(),
},
}
}
subcmdActionsGenRunnerToken = &cli.Command{
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",
Action: runGenerateActionsRunnerToken,
@ -36,10 +39,10 @@ var (
},
},
}
)
}
func runGenerateActionsRunnerToken(c *cli.Context) error {
ctx, cancel := installSignals()
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setting.MustInstalled()

View file

@ -15,56 +15,64 @@ import (
"forgejo.org/modules/log"
repo_module "forgejo.org/modules/repository"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdAdmin represents the available admin sub-command.
CmdAdmin = &cli.Command{
// CmdAdmin represents the available admin sub-command.
func cmdAdmin() *cli.Command {
return &cli.Command{
Name: "admin",
Usage: "Perform common administrative operations",
Subcommands: []*cli.Command{
subcmdUser,
subcmdRepoSyncReleases,
subcmdRegenerate,
subcmdAuth,
subcmdSendMail,
Commands: []*cli.Command{
subcmdUser(),
subcmdRepoSyncReleases(),
subcmdRegenerate(),
subcmdAuth(),
subcmdSendMail(),
},
}
}
subcmdRepoSyncReleases = &cli.Command{
func subcmdRepoSyncReleases() *cli.Command {
return &cli.Command{
Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases,
}
}
subcmdRegenerate = &cli.Command{
func subcmdRegenerate() *cli.Command {
return &cli.Command{
Name: "regenerate",
Usage: "Regenerate specific files",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
microcmdRegenHooks,
microcmdRegenKeys,
},
}
}
subcmdAuth = &cli.Command{
func subcmdAuth() *cli.Command {
return &cli.Command{
Name: "auth",
Usage: "Modify external auth providers",
Subcommands: []*cli.Command{
microcmdAuthAddOauth,
microcmdAuthUpdateOauth,
microcmdAuthAddLdapBindDn,
microcmdAuthUpdateLdapBindDn,
microcmdAuthAddLdapSimpleAuth,
microcmdAuthUpdateLdapSimpleAuth,
microcmdAuthAddSMTP,
microcmdAuthUpdateSMTP,
microcmdAuthList,
microcmdAuthDelete,
Commands: []*cli.Command{
microcmdAuthAddOauth(),
microcmdAuthUpdateOauth(),
microcmdAuthAddLdapBindDn(),
microcmdAuthUpdateLdapBindDn(),
microcmdAuthAddLdapSimpleAuth(),
microcmdAuthUpdateLdapSimpleAuth(),
microcmdAuthAddSMTP(),
microcmdAuthUpdateSMTP(),
microcmdAuthList(),
microcmdAuthDelete(),
},
}
}
subcmdSendMail = &cli.Command{
func subcmdSendMail() *cli.Command {
return &cli.Command{
Name: "sendmail",
Usage: "Send a message to all users",
Action: runSendMail,
@ -86,15 +94,17 @@ var (
},
},
}
}
idFlag = &cli.Int64Flag{
func idFlag() *cli.Int64Flag {
return &cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}
)
}
func runRepoSyncReleases(_ *cli.Context) error {
ctx, cancel := installSignals()
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
@ -13,17 +14,20 @@ import (
"forgejo.org/models/db"
auth_service "forgejo.org/services/auth"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
microcmdAuthDelete = &cli.Command{
func microcmdAuthDelete() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag},
Flags: []cli.Flag{idFlag()},
Action: runDeleteAuth,
}
microcmdAuthList = &cli.Command{
}
func microcmdAuthList() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
@ -54,10 +58,10 @@ var (
},
},
}
)
}
func runListAuth(c *cli.Context) error {
ctx, cancel := installSignals()
func runListAuth(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
@ -81,7 +85,7 @@ func runListAuth(c *cli.Context) error {
// loop through each source and print
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
fmt.Fprint(w, "ID\tName\tType\tEnabled\n")
for _, source := range authSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
}
@ -90,12 +94,12 @@ func runListAuth(c *cli.Context) error {
return nil
}
func runDeleteAuth(c *cli.Context) error {
func runDeleteAuth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -11,7 +11,7 @@ import (
"forgejo.org/models/auth"
"forgejo.org/services/auth/source/ldap"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
type (
@ -23,8 +23,8 @@ type (
}
)
var (
commonLdapCLIFlags = []cli.Flag{
func commonLdapCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "Authentication name.",
@ -102,8 +102,10 @@ var (
Usage: "The attribute of the users LDAP record containing the users avatar.",
},
}
}
ldapBindDnCLIFlags = append(commonLdapCLIFlags,
func ldapBindDnCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.",
@ -128,49 +130,59 @@ var (
Name: "page-size",
Usage: "Search page size.",
})
}
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
func ldapSimpleAuthCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "user-dn",
Usage: "The user's DN.",
})
}
microcmdAuthAddLdapBindDn = &cli.Command{
func microcmdAuthAddLdapBindDn() *cli.Command {
return &cli.Command{
Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().addLdapBindDn(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().addLdapBindDn(ctx, cli)
},
Flags: ldapBindDnCLIFlags,
Flags: ldapBindDnCLIFlags(),
}
}
microcmdAuthUpdateLdapBindDn = &cli.Command{
func microcmdAuthUpdateLdapBindDn() *cli.Command {
return &cli.Command{
Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().updateLdapBindDn(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().updateLdapBindDn(ctx, cli)
},
Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),
}
}
microcmdAuthAddLdapSimpleAuth = &cli.Command{
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
return &cli.Command{
Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().addLdapSimpleAuth(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().addLdapSimpleAuth(ctx, cli)
},
Flags: ldapSimpleAuthCLIFlags,
Flags: ldapSimpleAuthCLIFlags(),
}
}
microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
return &cli.Command{
Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().updateLdapSimpleAuth(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().updateLdapSimpleAuth(ctx, cli)
},
Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
}
)
}
// newAuthService creates a service with default functions.
func newAuthService() *authService {
@ -183,7 +195,7 @@ func newAuthService() *authService {
}
// parseAuthSource assigns values on authSource according to command line flags.
func parseAuthSource(c *cli.Context, authSource *auth.Source) {
func parseAuthSource(c *cli.Command, authSource *auth.Source) {
if c.IsSet("name") {
authSource.Name = c.String("name")
}
@ -202,7 +214,7 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) {
}
// parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("name") {
config.Name = c.String("name")
}
@ -289,7 +301,7 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
// getAuthSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type.
func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) {
func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) {
if err := argsSet(c, "id"); err != nil {
return nil, err
}
@ -307,12 +319,12 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authTyp
}
// addLdapBindDn adds a new LDAP via Bind DN authentication source.
func (a *authService) addLdapBindDn(c *cli.Context) error {
func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {
@ -336,8 +348,8 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
}
// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(c *cli.Context) error {
ctx, cancel := installSignals()
func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {
@ -358,12 +370,12 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
}
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {
@ -387,8 +399,8 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
}
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
ctx, cancel := installSignals()
func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {

View file

@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestAddLdapBindDn(t *testing.T) {
@ -225,12 +225,12 @@ func TestAddLdapBindDn(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthAddLdapBindDn.Flags
app := cli.Command{}
app.Flags = microcmdAuthAddLdapBindDn().Flags
app.Action = service.addLdapBindDn
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@ -454,12 +454,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
app := cli.Command{}
app.Flags = microcmdAuthAddLdapSimpleAuth().Flags
app.Action = service.addLdapSimpleAuth
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@ -915,12 +915,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthUpdateLdapBindDn.Flags
app := cli.Command{}
app.Flags = microcmdAuthUpdateLdapBindDn().Flags
app.Action = service.updateLdapBindDn
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@ -1303,12 +1303,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
app := cli.Command{}
app.Flags = microcmdAuthUpdateLdapSimpleAuth().Flags
app.Action = service.updateLdapSimpleAuth
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"net/url"
@ -11,11 +12,11 @@ import (
auth_model "forgejo.org/models/auth"
"forgejo.org/services/auth/source/oauth2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
oauthCLIFlags = []cli.Flag{
func oauthCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
@ -120,23 +121,27 @@ var (
Usage: "Activate automatic team membership removal depending on groups",
},
}
}
microcmdAuthAddOauth = &cli.Command{
func microcmdAuthAddOauth() *cli.Command {
return &cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: runAddOauth,
Flags: oauthCLIFlags,
Flags: oauthCLIFlags(),
}
}
microcmdAuthUpdateOauth = &cli.Command{
func microcmdAuthUpdateOauth() *cli.Command {
return &cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: runUpdateOauth,
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...),
}
)
}
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
@ -168,15 +173,15 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
}
}
func runAddOauth(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddOauth(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
config := parseOAuth2Config(c)
config := parseOAuth2Config(ctx, c)
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
@ -192,12 +197,12 @@ func runAddOauth(c *cli.Context) error {
})
}
func runUpdateOauth(c *cli.Context) error {
func runUpdateOauth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"strings"
@ -11,11 +12,11 @@ import (
"forgejo.org/modules/util"
"forgejo.org/services/auth/source/smtp"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
smtpCLIFlags = []cli.Flag{
func smtpCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
@ -71,23 +72,27 @@ var (
Value: true,
},
}
}
microcmdAuthAddSMTP = &cli.Command{
func microcmdAuthAddSMTP() *cli.Command {
return &cli.Command{
Name: "add-smtp",
Usage: "Add new SMTP authentication source",
Action: runAddSMTP,
Flags: smtpCLIFlags,
Flags: smtpCLIFlags(),
}
}
microcmdAuthUpdateSMTP = &cli.Command{
func microcmdAuthUpdateSMTP() *cli.Command {
return &cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP,
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...),
}
)
}
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
@ -123,8 +128,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
return nil
}
func runAddSMTP(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddSMTP(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
@ -163,12 +168,12 @@ func runAddSMTP(c *cli.Context) error {
})
}
func runUpdateSMTP(c *cli.Context) error {
func runUpdateSMTP(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,11 +4,13 @@
package cmd
import (
"context"
asymkey_model "forgejo.org/models/asymkey"
"forgejo.org/modules/graceful"
repo_service "forgejo.org/services/repository"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
@ -25,8 +27,8 @@ var (
}
)
func runRegenerateHooks(_ *cli.Context) error {
ctx, cancel := installSignals()
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
@ -35,8 +37,8 @@ func runRegenerateHooks(_ *cli.Context) error {
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
}
func runRegenerateKeys(_ *cli.Context) error {
ctx, cancel := installSignals()
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,18 +4,21 @@
package cmd
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var subcmdUser = &cli.Command{
Name: "user",
Usage: "Modify users",
Subcommands: []*cli.Command{
microcmdUserCreate,
microcmdUserList,
microcmdUserChangePassword,
microcmdUserDelete,
microcmdUserGenerateAccessToken,
microcmdUserMustChangePassword,
},
func subcmdUser() *cli.Command {
return &cli.Command{
Name: "user",
Usage: "Modify users",
Commands: []*cli.Command{
microcmdUserCreate(),
microcmdUserList(),
microcmdUserChangePassword(),
microcmdUserDelete(),
microcmdUserGenerateAccessToken(),
microcmdUserMustChangePassword(),
microcmdUserResetMFA(),
},
}
}

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
@ -13,40 +14,42 @@ import (
"forgejo.org/modules/setting"
user_service "forgejo.org/services/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserChangePassword = &cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "The user to change password for",
func microcmdUserChangePassword() *cli.Command {
return &cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "The user to change password for",
},
&cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Value: "",
Usage: "New password to set for user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password",
Value: true,
},
},
&cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Value: "",
Usage: "New password to set for user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password",
Value: true,
},
},
}
}
func runChangePassword(c *cli.Context) error {
func runChangePassword(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "username", "password"); err != nil {
return err
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"strings"
@ -15,75 +16,76 @@ import (
"forgejo.org/modules/optional"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserCreate = &cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
func microcmdUserCreate() *cli.Command {
return &cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
},
&cli.StringFlag{
Name: "username",
Usage: "Username",
},
&cli.StringFlag{
Name: "password",
Usage: "User password",
},
&cli.StringFlag{
Name: "email",
Usage: "User email address",
},
&cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
&cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
Value: true,
},
&cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
&cli.BoolFlag{
Name: "access-token",
Usage: "Generate access token for the user",
},
&cli.StringFlag{
Name: "access-token-name",
Usage: `Name of the generated access token`,
Value: "gitea-admin",
},
&cli.StringFlag{
Name: "access-token-scopes",
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
Value: "all",
},
&cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},
&cli.StringFlag{
Name: "fullname",
Usage: `The full, human-readable name of the user`,
},
},
&cli.StringFlag{
Name: "username",
Usage: "Username",
},
&cli.StringFlag{
Name: "password",
Usage: "User password",
},
&cli.StringFlag{
Name: "email",
Usage: "User email address",
},
&cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
&cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
Value: true,
DisableDefaultText: true,
},
&cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
&cli.BoolFlag{
Name: "access-token",
Usage: "Generate access token for the user",
},
&cli.StringFlag{
Name: "access-token-name",
Usage: `Name of the generated access token`,
Value: "gitea-admin",
},
&cli.StringFlag{
Name: "access-token-scopes",
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
Value: "all",
},
&cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},
&cli.StringFlag{
Name: "fullname",
Usage: `The full, human-readable name of the user`,
},
},
}
}
func runCreateUser(c *cli.Context) error {
func runCreateUser(ctx context.Context, c *cli.Command) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings()
@ -108,10 +110,10 @@ func runCreateUser(c *cli.Context) error {
username = c.String("username")
} else {
username = c.String("name")
_, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
_, _ = fmt.Fprint(c.Root().ErrWriter, "--name flag is deprecated. Use --username instead.\n")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"strings"
@ -12,41 +13,43 @@ import (
"forgejo.org/modules/storage"
user_service "forgejo.org/services/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserDelete = &cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
&cli.Int64Flag{
Name: "id",
Usage: "ID of user of the user to delete",
func microcmdUserDelete() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
&cli.Int64Flag{
Name: "id",
Usage: "ID of user of the user to delete",
},
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username of the user to delete",
},
&cli.StringFlag{
Name: "email",
Aliases: []string{"e"},
Usage: "Email of the user to delete",
},
&cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
},
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username of the user to delete",
},
&cli.StringFlag{
Name: "email",
Aliases: []string{"e"},
Usage: "Email of the user to delete",
},
&cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
},
Action: runDeleteUser,
Action: runDeleteUser,
}
}
func runDeleteUser(c *cli.Context) error {
func runDeleteUser(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return errors.New("You must provide the id, username or email of a user to delete")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,49 +4,52 @@
package cmd
import (
"context"
"errors"
"fmt"
auth_model "forgejo.org/models/auth"
user_model "forgejo.org/models/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserGenerateAccessToken = &cli.Command{
Name: "generate-access-token",
Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username",
func microcmdUserGenerateAccessToken() *cli.Command {
return &cli.Command{
Name: "generate-access-token",
Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username",
},
&cli.StringFlag{
Name: "token-name",
Aliases: []string{"t"},
Usage: "Token name",
Value: "gitea-admin",
},
&cli.BoolFlag{
Name: "raw",
Usage: "Display only the token value",
},
&cli.StringFlag{
Name: "scopes",
Value: "all",
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
},
},
&cli.StringFlag{
Name: "token-name",
Aliases: []string{"t"},
Usage: "Token name",
Value: "gitea-admin",
},
&cli.BoolFlag{
Name: "raw",
Usage: "Display only the token value",
},
&cli.StringFlag{
Name: "scopes",
Value: "all",
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
},
},
Action: runGenerateAccessToken,
Action: runGenerateAccessToken,
}
}
func runGenerateAccessToken(c *cli.Context) error {
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("you must provide a username to generate a token for")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,29 +4,32 @@
package cmd
import (
"context"
"fmt"
"os"
"text/tabwriter"
user_model "forgejo.org/models/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserList = &cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "admin",
Usage: "List only admin users",
func microcmdUserList() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "admin",
Usage: "List only admin users",
},
},
},
}
}
func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals()
func runListUsers(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
@ -41,7 +44,7 @@ func runListUsers(c *cli.Context) error {
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
if c.IsSet("admin") {
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\n")
for _, u := range users {
if u.IsAdmin {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
@ -49,7 +52,7 @@ func runListUsers(c *cli.Context) error {
}
} else {
twofa := user_model.UserList(users).GetTwoFaStatus(ctx)
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
fmt.Fprint(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
for _, u := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
}

View file

@ -4,38 +4,41 @@
package cmd
import (
"context"
"errors"
"fmt"
user_model "forgejo.org/models/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserMustChangePassword = &cli.Command{
Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "all",
Aliases: []string{"A"},
Usage: "All users must change password, except those explicitly excluded with --exclude",
func microcmdUserMustChangePassword() *cli.Command {
return &cli.Command{
Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "all",
Aliases: []string{"A"},
Usage: "All users must change password, except those explicitly excluded with --exclude",
},
&cli.StringSliceFlag{
Name: "exclude",
Aliases: []string{"e"},
Usage: "Do not change the must-change-password flag for these users",
},
&cli.BoolFlag{
Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it",
},
},
&cli.StringSliceFlag{
Name: "exclude",
Aliases: []string{"e"},
Usage: "Do not change the must-change-password flag for these users",
},
&cli.BoolFlag{
Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it",
},
},
}
}
func runMustChangePassword(c *cli.Context) error {
ctx, cancel := installSignals()
func runMustChangePassword(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if c.NArg() == 0 && !c.IsSet("all") {

View file

@ -0,0 +1,73 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
auth_model "forgejo.org/models/auth"
user_model "forgejo.org/models/user"
"github.com/urfave/cli/v3"
)
func microcmdUserResetMFA() *cli.Command {
return &cli.Command{
Name: "reset-mfa",
Usage: "Remove all two-factor authentication configurations for a user",
Action: runResetMFA,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "The user to update",
},
},
}
}
func runResetMFA(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "username"); err != nil {
return err
}
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
webAuthnList, err := auth_model.GetWebAuthnCredentialsByUID(ctx, user.ID)
if err != nil {
return err
}
for _, credential := range webAuthnList {
if _, err := auth_model.DeleteCredential(ctx, credential.ID, user.ID); err != nil {
return err
}
}
tfaModes, err := auth_model.GetTwoFactorByUID(ctx, user.ID)
if err == nil && tfaModes != nil {
if err := auth_model.DeleteTwoFactorByID(ctx, tfaModes.ID, user.ID); err != nil {
return err
}
} else {
if _, is := err.(auth_model.ErrTwoFactorNotEnrolled); !is {
return err
}
}
fmt.Printf("%s's two-factor authentication settings have been removed!\n", user.Name)
return nil
}

View file

@ -6,6 +6,7 @@
package cmd
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@ -20,47 +21,49 @@ import (
"strings"
"time"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdCert represents the available cert sub-command.
var CmdCert = &cli.Command{
Name: "cert",
Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server.
func cmdCert() *cli.Command {
return &cli.Command{
Name: "cert",
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.`,
Action: runCert,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
Action: runCert,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
},
&cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
&cli.IntFlag{
Name: "rsa-bits",
Value: 3072,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},
},
&cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
&cli.IntFlag{
Name: "rsa-bits",
Value: 3072,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},
},
}
}
func publicKey(priv any) any {
@ -89,7 +92,7 @@ func pemBlockForKey(priv any) *pem.Block {
}
}
func runCert(c *cli.Context) error {
func runCert(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "host"); err != nil {
return err
}

View file

@ -20,19 +20,21 @@ import (
"forgejo.org/modules/setting"
"forgejo.org/modules/util"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context.
func argsSet(c *cli.Context, args ...string) error {
func argsSet(c *cli.Command, args ...string) error {
for _, a := range args {
if !c.IsSet(a) {
return errors.New(a + " is not set")
}
if util.IsEmptyString(c.String(a)) {
return errors.New(a + " is required")
if s, ok := c.Value(a).(string); ok {
if util.IsEmptyString(s) {
return errors.New(a + " is required")
}
}
}
return nil
@ -73,8 +75,8 @@ If this is the intended configuration file complete the [database] section.`, se
return nil
}
func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
func installSignals(ctx context.Context) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(ctx)
go func() {
// install notify
signalChannel := make(chan os.Signal, 1)
@ -109,7 +111,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
func globalBool(c *cli.Context, name string) bool {
func globalBool(c *cli.Command, name string) bool {
for _, ctx := range c.Lineage() {
if ctx.Bool(name) {
return true
@ -120,16 +122,16 @@ func globalBool(c *cli.Context, name string) bool {
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
return func(c *cli.Context) error {
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context, cli *cli.Command) (context.Context, error) {
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
level := defaultLevel
if globalBool(c, "quiet") {
if globalBool(cli, "quiet") {
level = log.FATAL
}
if globalBool(c, "debug") || globalBool(c, "verbose") {
if globalBool(cli, "debug") || globalBool(cli, "verbose") {
level = log.TRACE
}
log.SetConsoleLogger(log.DEFAULT, "console-default", level)
return nil
return ctx, nil
}
}

View file

@ -1,65 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"os"
"strings"
"github.com/urfave/cli/v2"
)
// CmdDocs represents the available docs sub-command.
var CmdDocs = &cli.Command{
Name: "docs",
Usage: "Output CLI documentation",
Description: "A command to output Forgejo's CLI documentation, optionally to a file.",
Action: runDocs,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "man",
Usage: "Output man pages instead",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Path to output to instead of stdout (will overwrite if exists)",
},
},
}
func runDocs(ctx *cli.Context) error {
docs, err := ctx.App.ToMarkdown()
if ctx.Bool("man") {
docs, err = ctx.App.ToMan()
}
if err != nil {
return err
}
if !ctx.Bool("man") {
// Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040
firstHashtagIndex := strings.Index(docs, "#")
if firstHashtagIndex > 0 {
docs = docs[firstHashtagIndex:]
}
}
out := os.Stdout
if ctx.String("output") != "" {
fi, err := os.Create(ctx.String("output"))
if err != nil {
return err
}
defer fi.Close()
out = fi
}
_, err = fmt.Fprintln(out, docs)
return err
}

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"fmt"
golog "log"
"os"
@ -19,80 +20,86 @@ import (
"forgejo.org/modules/setting"
"forgejo.org/services/doctor"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
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.",
func cmdDoctor() *cli.Command {
return &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
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.",
Subcommands: []*cli.Command{
cmdDoctorCheck,
cmdRecreateTable,
cmdDoctorConvert,
},
Commands: []*cli.Command{
cmdDoctorCheck(),
cmdRecreateTable(),
cmdDoctorConvert(),
},
}
}
var 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.",
Action: runDoctorCheck,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "list",
Usage: "List the available checks",
func cmdDoctorCheck() *cli.Command {
return &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.",
Action: runDoctorCheck,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "list",
Usage: "List the available checks",
},
&cli.BoolFlag{
Name: "default",
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
},
&cli.StringSliceFlag{
Name: "run",
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
},
&cli.BoolFlag{
Name: "all",
Usage: "Run all the available checks",
},
&cli.BoolFlag{
Name: "fix",
Usage: "Automatically fix what we can",
},
&cli.StringFlag{
Name: "log-file",
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
},
&cli.BoolFlag{
Name: "color",
Aliases: []string{"H"},
Usage: "Use color for outputted information",
},
},
&cli.BoolFlag{
Name: "default",
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
},
&cli.StringSliceFlag{
Name: "run",
Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
},
&cli.BoolFlag{
Name: "all",
Usage: "Run all the available checks",
},
&cli.BoolFlag{
Name: "fix",
Usage: "Automatically fix what we can",
},
&cli.StringFlag{
Name: "log-file",
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`,
},
&cli.BoolFlag{
Name: "color",
Aliases: []string{"H"},
Usage: "Use color for outputted information",
},
},
}
}
var cmdRecreateTable = &cli.Command{
Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
Usage: "Print SQL commands sent",
func cmdRecreateTable() *cli.Command {
return &cli.Command{
Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
Usage: "Print SQL commands sent",
},
},
},
Description: `The database definitions Forgejo uses change across versions, sometimes changing default values and leaving old unused columns.
Description: `The database definitions Forgejo uses change across versions, sometimes changing default values and leaving old unused columns.
This command will cause Xorm to recreate tables, copying over the data and deleting the old table.
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
Action: runRecreateTable,
Action: runRecreateTable,
}
}
func runRecreateTable(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
// Redirect the default golog to here
@ -143,7 +150,7 @@ func runRecreateTable(ctx *cli.Context) error {
})
}
func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
func setupDoctorDefaultLogger(ctx *cli.Command, colorize bool) {
// Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
@ -165,8 +172,8 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
}
}
func runDoctorCheck(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
colorize := log.CanColorStdout

View file

@ -4,25 +4,28 @@
package cmd
import (
"context"
"fmt"
"forgejo.org/models/db"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// cmdDoctorConvert represents the available convert sub-command.
var cmdDoctorConvert = &cli.Command{
Name: "convert",
Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
Action: runDoctorConvert,
func cmdDoctorConvert() *cli.Command {
return &cli.Command{
Name: "convert",
Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
Action: runDoctorConvert,
}
}
func runDoctorConvert(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runDoctorConvert(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -11,7 +11,7 @@ import (
"forgejo.org/services/doctor"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestDoctorRun(t *testing.T) {
@ -22,12 +22,12 @@ func TestDoctorRun(t *testing.T) {
SkipDatabaseInitialization: true,
})
app := cli.NewApp()
app.Commands = []*cli.Command{cmdDoctorCheck}
err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
app := cli.Command{}
app.Commands = []*cli.Command{cmdDoctorCheck()}
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
require.NoError(t, err)
err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"})
require.ErrorContains(t, err, `unknown checks: "no-such"`)
err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"})
require.ErrorContains(t, err, `unknown checks: "no-such"`)
}

View file

@ -5,6 +5,8 @@
package cmd
import (
"context"
"errors"
"fmt"
"io"
"os"
@ -22,7 +24,7 @@ import (
"code.forgejo.org/go-chi/session"
"github.com/mholt/archiver/v3"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
@ -83,6 +85,10 @@ func (o *outputType) Set(value string) error {
return fmt.Errorf("allowed values are %s", o.Join())
}
func (o *outputType) Get() any {
return o.String()
}
func (o outputType) String() string {
if o.selected == "" {
return o.Default
@ -96,79 +102,81 @@ var outputTypeEnum = &outputType{
}
// CmdDump represents the available dump sub-command.
var CmdDump = &cli.Command{
Name: "dump",
Usage: "Dump Forgejo files and database",
Description: `Dump compresses all related files and database into zip file.
func cmdDump() *cli.Command {
return &cli.Command{
Name: "dump",
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`,
Action: runDump,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "file",
Aliases: []string{"f"},
Value: fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()),
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
Action: runDump,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "file",
Aliases: []string{"f"},
Value: fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()),
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"V"},
Usage: "Show process details",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display warnings and errors",
},
&cli.StringFlag{
Name: "tempdir",
Aliases: []string{"t"},
Usage: "Temporary dir path",
},
&cli.StringFlag{
Name: "database",
Aliases: []string{"d"},
Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres",
},
&cli.BoolFlag{
Name: "skip-repository",
Aliases: []string{"R"},
Usage: "Skip repositories",
},
&cli.BoolFlag{
Name: "skip-log",
Aliases: []string{"L"},
Usage: "Skip logs",
},
&cli.BoolFlag{
Name: "skip-custom-dir",
Usage: "Skip custom directory",
},
&cli.BoolFlag{
Name: "skip-lfs-data",
Usage: "Skip LFS data",
},
&cli.BoolFlag{
Name: "skip-attachment-data",
Usage: "Skip attachment data",
},
&cli.BoolFlag{
Name: "skip-package-data",
Usage: "Skip package data",
},
&cli.BoolFlag{
Name: "skip-index",
Usage: "Skip bleve index data",
},
&cli.BoolFlag{
Name: "skip-repo-archives",
Usage: "Skip repository archives",
},
&cli.GenericFlag{
Name: "type",
Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
},
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"V"},
Usage: "Show process details",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display warnings and errors",
},
&cli.StringFlag{
Name: "tempdir",
Aliases: []string{"t"},
Usage: "Temporary dir path",
},
&cli.StringFlag{
Name: "database",
Aliases: []string{"d"},
Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres",
},
&cli.BoolFlag{
Name: "skip-repository",
Aliases: []string{"R"},
Usage: "Skip repositories",
},
&cli.BoolFlag{
Name: "skip-log",
Aliases: []string{"L"},
Usage: "Skip logs",
},
&cli.BoolFlag{
Name: "skip-custom-dir",
Usage: "Skip custom directory",
},
&cli.BoolFlag{
Name: "skip-lfs-data",
Usage: "Skip LFS data",
},
&cli.BoolFlag{
Name: "skip-attachment-data",
Usage: "Skip attachment data",
},
&cli.BoolFlag{
Name: "skip-package-data",
Usage: "Skip package data",
},
&cli.BoolFlag{
Name: "skip-index",
Usage: "Skip bleve index data",
},
&cli.BoolFlag{
Name: "skip-repo-archives",
Usage: "Skip repository archives",
},
&cli.GenericFlag{
Name: "type",
Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
},
},
}
}
func fatal(format string, args ...any) {
@ -176,7 +184,7 @@ func fatal(format string, args ...any) {
log.Fatal(format, args...)
}
func runDump(ctx *cli.Context) error {
func runDump(stdCtx context.Context, ctx *cli.Command) error {
var file *os.File
fileName := ctx.String("file")
outType := ctx.String("type")
@ -212,16 +220,16 @@ func runDump(ctx *cli.Context) error {
if !setting.InstallLock {
log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
return fmt.Errorf("forgejo is not initialized")
return errors.New("forgejo is not initialized")
}
setting.LoadSettings() // cannot access session settings otherwise
verbose := ctx.Bool("verbose")
if verbose && ctx.Bool("quiet") {
return fmt.Errorf("--quiet and --verbose cannot both be set")
return errors.New("--quiet and --verbose cannot both be set")
}
stdCtx, cancel := installSignals()
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
err := db.InitEngine(stdCtx)

View file

@ -19,68 +19,70 @@ import (
"forgejo.org/services/convert"
"forgejo.org/services/migrations"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdDumpRepository represents the available dump repository sub-command.
var 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.",
Action: runDumpRepository,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "git_service",
Value: "",
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
},
&cli.StringFlag{
Name: "repo_dir",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to store the data",
},
&cli.StringFlag{
Name: "clone_addr",
Value: "",
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
},
&cli.StringFlag{
Name: "auth_username",
Value: "",
Usage: "The username to visit the clone_addr",
},
&cli.StringFlag{
Name: "auth_password",
Value: "",
Usage: "The password to visit the clone_addr",
},
&cli.StringFlag{
Name: "auth_token",
Value: "",
Usage: "The personal token to visit the clone_addr",
},
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "The data will be stored on a directory with owner name if not empty",
},
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "The data will be stored on a directory with repository name if not empty",
},
&cli.StringFlag{
Name: "units",
Value: "",
Usage: `Which items will be migrated, one or more units should be separated as comma.
func cmdDumpRepository() *cli.Command {
return &cli.Command{
Name: "dump-repo",
Usage: "Dump the repository from git/github/gitea/gitlab",
Description: "This is a command for dumping the repository data.",
Action: runDumpRepository,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "git_service",
Value: "",
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
},
&cli.StringFlag{
Name: "repo_dir",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to store the data",
},
&cli.StringFlag{
Name: "clone_addr",
Value: "",
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
},
&cli.StringFlag{
Name: "auth_username",
Value: "",
Usage: "The username to visit the clone_addr",
},
&cli.StringFlag{
Name: "auth_password",
Value: "",
Usage: "The password to visit the clone_addr",
},
&cli.StringFlag{
Name: "auth_token",
Value: "",
Usage: "The personal token to visit the clone_addr",
},
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "The data will be stored on a directory with owner name if not empty",
},
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "The data will be stored on a directory with repository name if not empty",
},
&cli.StringFlag{
Name: "units",
Value: "",
Usage: `Which items will be migrated, one or more units should be separated as comma.
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
},
},
},
}
}
func runDumpRepository(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
@ -19,23 +20,25 @@ import (
"forgejo.org/modules/util"
"github.com/gobwas/glob"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdEmbedded represents the available extract sub-command.
var (
CmdEmbedded = &cli.Command{
func cmdEmbedded() *cli.Command {
return &cli.Command{
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",
Subcommands: []*cli.Command{
subcmdList,
subcmdView,
subcmdExtract,
Commands: []*cli.Command{
subcmdList(),
subcmdView(),
subcmdExtract(),
},
}
}
subcmdList = &cli.Command{
func subcmdList() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List files matching the given pattern",
Action: runList,
@ -47,8 +50,10 @@ var (
},
},
}
}
subcmdView = &cli.Command{
func subcmdView() *cli.Command {
return &cli.Command{
Name: "view",
Usage: "View a file matching the given pattern",
Action: runView,
@ -60,8 +65,10 @@ var (
},
},
}
}
subcmdExtract = &cli.Command{
func subcmdExtract() *cli.Command {
return &cli.Command{
Name: "extract",
Usage: "Extract resources",
Action: runExtract,
@ -90,9 +97,9 @@ var (
},
},
}
}
matchedAssetFiles []assetFile
)
var matchedAssetFiles []assetFile
type assetFile struct {
fs *assetfs.LayeredFS
@ -100,7 +107,7 @@ type assetFile struct {
path string
}
func initEmbeddedExtractor(c *cli.Context) error {
func initEmbeddedExtractor(_ context.Context, c *cli.Command) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args().Slice())
@ -115,32 +122,32 @@ func initEmbeddedExtractor(c *cli.Context) error {
return nil
}
func runList(c *cli.Context) error {
if err := runListDo(c); err != nil {
func runList(ctx context.Context, c *cli.Command) error {
if err := runListDo(ctx, c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runView(c *cli.Context) error {
if err := runViewDo(c); err != nil {
func runView(ctx context.Context, c *cli.Command) error {
if err := runViewDo(ctx, c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runExtract(c *cli.Context) error {
if err := runExtractDo(c); err != nil {
func runExtract(ctx context.Context, c *cli.Command) error {
if err := runExtractDo(ctx, c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runListDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
func runListDo(ctx context.Context, c *cli.Command) error {
if err := initEmbeddedExtractor(ctx, c); err != nil {
return err
}
@ -151,8 +158,8 @@ func runListDo(c *cli.Context) error {
return nil
}
func runViewDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
func runViewDo(ctx context.Context, c *cli.Command) error {
if err := initEmbeddedExtractor(ctx, c); err != nil {
return err
}
@ -174,8 +181,8 @@ func runViewDo(c *cli.Context) error {
return nil
}
func runExtractDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
func runExtractDo(ctx context.Context, c *cli.Command) error {
if err := initEmbeddedExtractor(ctx, c); err != nil {
return err
}
@ -271,7 +278,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
return nil
}
func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) {
fs := assetfs.Layered(layer)
files, err := fs.ListAllFiles(".", true)
if err != nil {

View file

@ -6,6 +6,7 @@ package forgejo
import (
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
@ -16,14 +17,14 @@ import (
"forgejo.org/modules/setting"
private_routers "forgejo.org/routers/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CmdActions(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "actions",
Usage: "Commands for managing Forgejo Actions",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
SubcmdActionsGenerateRunnerToken(ctx),
SubcmdActionsGenerateRunnerSecret(ctx),
SubcmdActionsRegister(ctx),
@ -36,7 +37,7 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command {
Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server",
Before: prepareWorkPathAndCustomConf(ctx),
Action: func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) },
Action: RunGenerateActionsRunnerToken,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "scope",
@ -52,7 +53,7 @@ func SubcmdActionsGenerateRunnerSecret(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "generate-secret",
Usage: "Generate a secret suitable for input to the register subcommand",
Action: func(cliCtx *cli.Context) error { return RunGenerateSecret(ctx, cliCtx) },
Action: RunGenerateSecret,
}
}
@ -61,7 +62,7 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
Name: "register",
Usage: "Idempotent registration of a runner using a shared secret",
Before: prepareWorkPathAndCustomConf(ctx),
Action: func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) },
Action: RunRegister,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "secret",
@ -105,26 +106,26 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
}
}
func readSecret(ctx context.Context, cliCtx *cli.Context) (string, error) {
if cliCtx.IsSet("secret") {
return cliCtx.String("secret"), nil
func readSecret(ctx context.Context, cli *cli.Command) (string, error) {
if cli.IsSet("secret") {
return cli.String("secret"), nil
}
if cliCtx.IsSet("secret-stdin") {
if cli.IsSet("secret-stdin") {
buf, err := io.ReadAll(ContextGetStdin(ctx))
if err != nil {
return "", err
}
return string(buf), nil
}
if cliCtx.IsSet("secret-file") {
path := cliCtx.String("secret-file")
if cli.IsSet("secret-file") {
path := cli.String("secret-file")
buf, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(buf), nil
}
return "", fmt.Errorf("at least one of the --secret, --secret-stdin, --secret-file options is required")
return "", errors.New("at least one of the --secret, --secret-stdin, --secret-file options is required")
}
func validateSecret(secret string) error {
@ -138,18 +139,18 @@ func validateSecret(secret string) error {
return nil
}
func getLabels(cliCtx *cli.Context) (*[]string, error) {
if !cliCtx.Bool("keep-labels") {
lblValue := strings.Split(cliCtx.String("labels"), ",")
func getLabels(cli *cli.Command) (*[]string, error) {
if !cli.Bool("keep-labels") {
lblValue := strings.Split(cli.String("labels"), ",")
return &lblValue, nil
}
if cliCtx.String("labels") != "" {
return nil, fmt.Errorf("--labels and --keep-labels should not be used together")
if cli.String("labels") != "" {
return nil, errors.New("--labels and --keep-labels should not be used together")
}
return nil, nil
}
func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
func RunRegister(ctx context.Context, cli *cli.Command) error {
var cancel context.CancelFunc
if !ContextGetNoInit(ctx) {
ctx, cancel = installSignals(ctx)
@ -161,17 +162,17 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
}
setting.MustInstalled()
secret, err := readSecret(ctx, cliCtx)
secret, err := readSecret(ctx, cli)
if err != nil {
return err
}
if err := validateSecret(secret); err != nil {
return err
}
scope := cliCtx.String("scope")
name := cliCtx.String("name")
version := cliCtx.String("version")
labels, err := getLabels(cliCtx)
scope := cli.String("scope")
name := cli.String("name")
version := cli.String("version")
labels, err := getLabels(cli)
if err != nil {
return err
}
@ -209,7 +210,7 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
return nil
}
func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error {
func RunGenerateSecret(ctx context.Context, cli *cli.Command) error {
runner := actions_model.ActionRunner{}
if err := runner.GenerateToken(); err != nil {
return err
@ -220,7 +221,7 @@ func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error {
return nil
}
func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) error {
func RunGenerateActionsRunnerToken(ctx context.Context, cli *cli.Command) error {
if !ContextGetNoInit(ctx) {
var cancel context.CancelFunc
ctx, cancel = installSignals(ctx)
@ -229,7 +230,7 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) err
setting.MustInstalled()
scope := cliCtx.String("scope")
scope := cli.String("scope")
respText, extra := private.GenerateActionsRunnerToken(ctx, scope)
if extra.HasError() {

View file

@ -4,14 +4,13 @@
package forgejo
import (
"context"
"fmt"
"testing"
"forgejo.org/services/context"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestActions_getLabels(t *testing.T) {
@ -54,21 +53,21 @@ func TestActions_getLabels(t *testing.T) {
},
}
flags := SubcmdActionsRegister(context.Context{}).Flags
flags := SubcmdActionsRegister(t.Context()).Flags
for _, c := range cases {
t.Run(fmt.Sprintf("args: %v", c.args), func(t *testing.T) {
// Create a copy of command to test
var result *resultType
app := cli.NewApp()
app := cli.Command{}
app.Flags = flags
app.Action = func(ctx *cli.Context) error {
app.Action = func(_ context.Context, ctx *cli.Command) error {
labels, err := getLabels(ctx)
result = &resultType{labels, err}
return nil
}
// Run it
_ = app.Run(c.args)
_ = app.Run(t.Context(), c.args)
// Test the results
require.NotNil(t, result)

View file

@ -20,7 +20,7 @@ import (
f3_cmd "code.forgejo.org/f3/gof3/v3/cmd"
f3_logger "code.forgejo.org/f3/gof3/v3/logger"
f3_util "code.forgejo.org/f3/gof3/v3/util"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CmdF3(ctx context.Context) *cli.Command {
@ -28,21 +28,21 @@ func CmdF3(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "f3",
Usage: "F3",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
SubcmdF3Mirror(ctx),
},
}
}
func SubcmdF3Mirror(ctx context.Context) *cli.Command {
mirrorCmd := f3_cmd.CreateCmdMirror(ctx)
mirrorCmd := f3_cmd.CreateCmdMirror()
mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx)
f3Action := mirrorCmd.Action
mirrorCmd.Action = func(c *cli.Context) error { return runMirror(ctx, c, f3Action) }
mirrorCmd.Action = func(ctx context.Context, cli *cli.Command) error { return runMirror(ctx, cli, f3Action) }
return mirrorCmd
}
func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error {
func runMirror(ctx context.Context, c *cli.Command, action cli.ActionFunc) error {
setting.LoadF3Setting()
if !setting.F3.Enabled {
return errors.New("F3 is disabled, it is not ready to be used and is only present for development purposes")
@ -69,7 +69,7 @@ func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error
}
}
err := action(c)
err := action(ctx, c)
if panicError, ok := err.(f3_util.PanicError); ok {
log.Debug("F3 Stack trace\n%s", panicError.Stack())
}

View file

@ -16,7 +16,7 @@ import (
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
type key int
@ -34,7 +34,7 @@ func CmdForgejo(ctx context.Context) *cli.Command {
Name: "forgejo-cli",
Usage: "Forgejo CLI",
Flags: []cli.Flag{},
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CmdActions(ctx),
CmdF3(ctx),
},
@ -147,12 +147,12 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er
return cli.Exit(extra.Error, 1)
}
func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) error {
return func(c *cli.Context) error {
func prepareWorkPathAndCustomConf(ctx context.Context) func(ctx context.Context, cli *cli.Command) (context.Context, error) {
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
if !ContextGetNoInit(ctx) {
var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags
for _, curCtx := range c.Lineage() {
for _, curCtx := range cli.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
@ -165,6 +165,6 @@ func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) erro
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
}
return nil
return ctx, nil
}
}

View file

@ -5,56 +5,65 @@
package cmd
import (
"context"
"fmt"
"os"
"forgejo.org/modules/generate"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdGenerate represents the available generate sub-command.
CmdGenerate = &cli.Command{
// CmdGenerate represents the available generate sub-command.
func cmdGenerate() *cli.Command {
return &cli.Command{
Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens",
Subcommands: []*cli.Command{
subcmdSecret,
Commands: []*cli.Command{
subcmdSecret(),
},
}
}
subcmdSecret = &cli.Command{
func subcmdSecret() *cli.Command {
return &cli.Command{
Name: "secret",
Usage: "Generate a secret token",
Subcommands: []*cli.Command{
microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey,
Commands: []*cli.Command{
microcmdGenerateInternalToken(),
microcmdGenerateLfsJwtSecret(),
microcmdGenerateSecretKey(),
},
}
}
microcmdGenerateInternalToken = &cli.Command{
func microcmdGenerateInternalToken() *cli.Command {
return &cli.Command{
Name: "INTERNAL_TOKEN",
Usage: "Generate a new INTERNAL_TOKEN",
Action: runGenerateInternalToken,
}
}
microcmdGenerateLfsJwtSecret = &cli.Command{
func microcmdGenerateLfsJwtSecret() *cli.Command {
return &cli.Command{
Name: "JWT_SECRET",
Aliases: []string{"LFS_JWT_SECRET"},
Usage: "Generate a new JWT_SECRET",
Action: runGenerateLfsJwtSecret,
}
}
microcmdGenerateSecretKey = &cli.Command{
func microcmdGenerateSecretKey() *cli.Command {
return &cli.Command{
Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey,
}
)
}
func runGenerateInternalToken(c *cli.Context) error {
func runGenerateInternalToken(ctx context.Context, c *cli.Command) error {
internalToken, err := generate.NewInternalToken()
if err != nil {
return err
@ -63,25 +72,25 @@ func runGenerateInternalToken(c *cli.Context) error {
fmt.Printf("%s", internalToken)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
fmt.Println()
}
return nil
}
func runGenerateLfsJwtSecret(c *cli.Context) error {
func runGenerateLfsJwtSecret(ctx context.Context, c *cli.Command) error {
_, jwtSecretBase64 := generate.NewJwtSecret()
fmt.Printf("%s", jwtSecretBase64)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
fmt.Print("\n")
}
return nil
}
func runGenerateSecretKey(c *cli.Context) error {
func runGenerateSecretKey(ctx context.Context, c *cli.Command) error {
secretKey, err := generate.NewSecretKey()
if err != nil {
return err
@ -90,7 +99,7 @@ func runGenerateSecretKey(c *cli.Context) error {
fmt.Printf("%s", secretKey)
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n")
fmt.Print("\n")
}
return nil

View file

@ -21,29 +21,31 @@ import (
repo_module "forgejo.org/modules/repository"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
hookBatchSize = 30
)
var (
// CmdHook represents the available hooks sub-command.
CmdHook = &cli.Command{
// CmdHook represents the available hooks sub-command.
func cmdHook() *cli.Command {
return &cli.Command{
Name: "hook",
Usage: "(internal) Should only be called by Git",
Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Subcommands: []*cli.Command{
subcmdHookPreReceive,
subcmdHookUpdate,
subcmdHookPostReceive,
subcmdHookProcReceive,
Commands: []*cli.Command{
subcmdHookPreReceive(),
subcmdHookUpdate(),
subcmdHookPostReceive(),
subcmdHookProcReceive(),
},
}
}
subcmdHookPreReceive = &cli.Command{
func subcmdHookPreReceive() *cli.Command {
return &cli.Command{
Name: "pre-receive",
Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git",
@ -54,7 +56,10 @@ var (
},
},
}
subcmdHookUpdate = &cli.Command{
}
func subcmdHookUpdate() *cli.Command {
return &cli.Command{
Name: "update",
Usage: "Delegate update Git hook",
Description: "This command should only be called by Git",
@ -65,7 +70,10 @@ var (
},
},
}
subcmdHookPostReceive = &cli.Command{
}
func subcmdHookPostReceive() *cli.Command {
return &cli.Command{
Name: "post-receive",
Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git",
@ -76,8 +84,11 @@ var (
},
},
}
// Note: new hook since git 2.29
subcmdHookProcReceive = &cli.Command{
}
// Note: new hook since git 2.29
func subcmdHookProcReceive() *cli.Command {
return &cli.Command{
Name: "proc-receive",
Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git",
@ -88,7 +99,7 @@ var (
},
},
}
)
}
type delayWriter struct {
internal io.Writer
@ -161,11 +172,11 @@ func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil
}
func runHookPreReceive(c *cli.Context) error {
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)
@ -247,7 +258,7 @@ Forgejo or set your environment appropriately.`, "")
newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName
count++
fmt.Fprintf(out, "*")
fmt.Fprint(out, "*")
if count >= hookBatchSize {
fmt.Fprintf(out, " Checking %d references\n", count)
@ -263,10 +274,10 @@ Forgejo or set your environment appropriately.`, "")
lastline = 0
}
} else {
fmt.Fprintf(out, ".")
fmt.Fprint(out, ".")
}
if lastline >= hookBatchSize {
fmt.Fprintf(out, "\n")
fmt.Fprint(out, "\n")
lastline = 0
}
}
@ -283,7 +294,7 @@ Forgejo or set your environment appropriately.`, "")
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
}
} else if lastline > 0 {
fmt.Fprintf(out, "\n")
fmt.Fprint(out, "\n")
}
fmt.Fprintf(out, "Checked %d references in total\n", total)
@ -291,13 +302,13 @@ Forgejo or set your environment appropriately.`, "")
}
// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
func runHookUpdate(c *cli.Context) error {
func runHookUpdate(ctx context.Context, c *cli.Command) error {
// Now if we're an internal don't do anything else
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if c.NArg() != 3 {
@ -323,8 +334,8 @@ func runHookUpdate(c *cli.Context) error {
return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "")
}
func runHookPostReceive(c *cli.Context) error {
ctx, cancel := installSignals()
func runHookPostReceive(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)
@ -399,7 +410,7 @@ Forgejo or set your environment appropriately.`, "")
continue
}
fmt.Fprintf(out, ".")
fmt.Fprint(out, ".")
oldCommitIDs[count] = string(fields[0])
newCommitIDs[count] = string(fields[1])
refFullNames[count] = git.RefName(fields[2])
@ -487,8 +498,8 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) {
}
}
func runHookProcReceive(c *cli.Context) error {
ctx, cancel := installSignals()
func runHookProcReceive(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)

View file

@ -19,7 +19,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Capture what's being written into a standard file descriptor.
@ -134,14 +134,14 @@ func TestDelayWriter(t *testing.T) {
defer ts.Close()
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
app := cli.NewApp()
app.Commands = []*cli.Command{subcmdHookPreReceive}
app := cli.Command{}
app.Commands = []*cli.Command{subcmdHookPreReceive()}
t.Run("Should delay", func(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
finish := captureOutput(t, os.Stdout)
err = app.Run([]string{"./forgejo", "pre-receive"})
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
require.NoError(t, err)
out := finish()
@ -153,7 +153,7 @@ func TestDelayWriter(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
finish := captureOutput(t, os.Stdout)
err = app.Run([]string{"./forgejo", "pre-receive"})
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
require.NoError(t, err)
out := finish()
@ -163,15 +163,15 @@ func TestDelayWriter(t *testing.T) {
}
func TestRunHookUpdate(t *testing.T) {
app := cli.NewApp()
app.Commands = []*cli.Command{subcmdHookUpdate}
app := cli.Command{}
app.Commands = []*cli.Command{subcmdHookUpdate()}
t.Run("Removal of internal reference", func(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
defer test.MockVariableValue(&setting.IsProd, false)()
finish := captureOutput(t, os.Stderr)
err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
out := finish()
require.Error(t, err)
@ -183,7 +183,7 @@ func TestRunHookUpdate(t *testing.T) {
defer test.MockVariableValue(&setting.IsProd, false)()
finish := captureOutput(t, os.Stderr)
err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
out := finish()
require.Error(t, err)
@ -191,12 +191,12 @@ func TestRunHookUpdate(t *testing.T) {
})
t.Run("Removal of branch", func(t *testing.T) {
err := app.Run([]string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
require.NoError(t, err)
})
t.Run("Not enough arguments", func(t *testing.T) {
err := app.Run([]string{"./forgejo", "update"})
err := app.Run(t.Context(), []string{"./forgejo", "update"})
require.NoError(t, err)
})
}

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"strings"
@ -11,45 +12,47 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdKeys represents the available keys sub-command
var 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),
Action: runKeys,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "expected",
Aliases: []string{"e"},
Value: "git",
Usage: "Expected user for whom provide key commands",
func cmdKeys() *cli.Command {
return &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),
Action: runKeys,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "expected",
Aliases: []string{"e"},
Value: "git",
Usage: "Expected user for whom provide key commands",
},
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "Username trying to log in by SSH",
},
&cli.StringFlag{
Name: "type",
Aliases: []string{"t"},
Value: "",
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
},
&cli.StringFlag{
Name: "content",
Aliases: []string{"k"},
Value: "",
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
},
},
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "Username trying to log in by SSH",
},
&cli.StringFlag{
Name: "type",
Aliases: []string{"t"},
Value: "",
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
},
&cli.StringFlag{
Name: "content",
Aliases: []string{"k"},
Value: "",
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
},
},
}
}
func runKeys(c *cli.Context) error {
func runKeys(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("No username provided")
}
@ -68,7 +71,7 @@ func runKeys(c *cli.Context) error {
return errors.New("No key type and content provided")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)
@ -78,6 +81,6 @@ func runKeys(c *cli.Context) error {
if extra.Error != nil {
return extra.Error
}
_, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
_, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text))
return nil
}

View file

@ -4,16 +4,17 @@
package cmd
import (
"context"
"fmt"
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func runSendMail(c *cli.Context) error {
ctx, cancel := installSignals()
func runSendMail(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setting.MustInstalled()

View file

@ -14,7 +14,7 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// cmdHelp is our own help subcommand with more information
@ -25,18 +25,18 @@ func cmdHelp() *cli.Command {
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *cli.Context) (err error) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
Action: func(ctx context.Context, c *cli.Command) (err error) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Forgejo
targetCmdIdx := 0
if c.Command.Name == "help" {
if c.Name == "help" {
targetCmdIdx = 1
}
if lineage[targetCmdIdx+1].Command != nil {
err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
if targetCmdIdx+1 < len(lineage) {
err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1], lineage[targetCmdIdx].Name)
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
_, _ = fmt.Fprintf(c.Root().Writer, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
@ -77,25 +77,25 @@ func appGlobalFlags() []cli.Flag {
}
}
func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
func prepareSubcommandWithConfig(command *cli.Command, globalFlags func() []cli.Flag) {
command.Flags = append(globalFlags(), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
command.Subcommands = append(command.Subcommands, cmdHelp())
command.Commands = append(command.Commands, cmdHelp())
}
for i := range command.Subcommands {
prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
for i := range command.Commands {
prepareSubcommandWithConfig(command.Commands[i], globalFlags)
}
}
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(_ context.Context, _ *cli.Command) error {
return func(ctx context.Context, cli *cli.Command) error {
var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags
for _, curCtx := range ctx.Lineage() {
for _, curCtx := range cli.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
@ -107,15 +107,15 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
}
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if ctx.Bool("help") || action == nil {
if cli.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx)
return cmdHelp().Action(ctx, cli)
}
return action(ctx)
return action(ctx, cli)
}
}
func NewMainApp(version, versionExtra string) *cli.App {
func NewMainApp(version, versionExtra string) *cli.Command {
path, err := os.Executable()
if err != nil {
panic(err)
@ -124,7 +124,7 @@ func NewMainApp(version, versionExtra string) *cli.App {
subCmdsStandalone := make([]*cli.Command, 0, 10)
subCmdWithConfig := make([]*cli.Command, 0, 10)
globalFlags := make([]cli.Flag, 0, 10)
globalFlags := func() []cli.Flag { return []cli.Flag{} }
//
// If the executable is forgejo-cli, provide a Forgejo specific CLI
@ -133,14 +133,16 @@ func NewMainApp(version, versionExtra string) *cli.App {
if executable == "forgejo-cli" {
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background()))
subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background()))
globalFlags = append(globalFlags, []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
},
&cli.BoolFlag{
Name: "verbose",
},
}...)
globalFlags = func() []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
},
&cli.BoolFlag{
Name: "verbose",
},
}
}
} else {
//
// Otherwise provide a Gitea compatible CLI which includes Forgejo
@ -149,55 +151,54 @@ func NewMainApp(version, versionExtra string) *cli.App {
// binary and rename it to forgejo if they want.
//
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background()))
subCmdWithConfig = append(subCmdWithConfig, CmdActions)
subCmdWithConfig = append(subCmdWithConfig, cmdActions())
}
return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags)
}
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs []cli.Flag) *cli.App {
app := cli.NewApp()
app.HelpName = "forgejo"
app.Name = "Forgejo"
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs func() []cli.Flag) *cli.Command {
app := &cli.Command{}
app.Name = "forgejo"
app.Usage = "Beyond coding. We forge."
app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
app.Version = version + versionExtra
app.EnableBashCompletion = true
app.EnableShellCompletion = true
// these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
CmdWeb,
CmdServ,
CmdHook,
CmdKeys,
CmdDump,
CmdAdmin,
CmdMigrate,
CmdDoctor,
CmdManager,
CmdEmbedded,
CmdMigrateStorage,
CmdDumpRepository,
CmdRestoreRepository,
cmdWeb(),
cmdServ(),
cmdHook(),
cmdKeys(),
cmdDump(),
cmdAdmin(),
cmdMigrate(),
cmdDoctor(),
cmdManager(),
cmdEmbedded(),
cmdMigrateStorage(),
cmdDumpRepository(),
cmdRestoreRepository(),
}
subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...)
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
CmdCert,
CmdGenerate,
CmdDocs,
cmdCert(),
cmdGenerate(),
}
subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...)
app.DefaultCommand = CmdWeb.Name
app.DefaultCommand = cmdWeb().Name
globalFlags := appGlobalFlags()
globalFlags = append(globalFlags, globalFlagsArgs...)
globalFlags := func() []cli.Flag {
return append(appGlobalFlags(), globalFlagsArgs()...)
}
app.Flags = append(app.Flags, cli.VersionFlag)
app.Flags = append(app.Flags, globalFlags...)
app.Flags = append(app.Flags, globalFlags()...)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig {
@ -210,8 +211,8 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
return app
}
func RunMainApp(app *cli.App, args ...string) error {
err := app.Run(args)
func RunMainApp(app *cli.Command, args ...string) error {
err := app.Run(context.Background(), args)
if err == nil {
return nil
}
@ -220,7 +221,7 @@ func RunMainApp(app *cli.App, args ...string) error {
cli.OsExiter(1)
return err
}
_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
_, _ = fmt.Fprintf(app.Root().ErrWriter, "Command error: %v\n", err)
cli.OsExiter(1)
return err
}

View file

@ -4,6 +4,8 @@
package cmd
import (
"context"
"errors"
"fmt"
"io"
"path/filepath"
@ -16,7 +18,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestMain(m *testing.M) {
@ -27,10 +29,10 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
}
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
func newTestApp(testCmdAction func(_ context.Context, ctx *cli.Command) error) *cli.Command {
app := NewMainApp("version", "version-extra")
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
prepareSubcommandWithConfig(testCmd, appGlobalFlags)
app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name
return app
@ -42,7 +44,7 @@ type runResult struct {
ExitCode int
}
func runTestApp(app *cli.App, args ...string) (runResult, error) {
func runTestApp(app *cli.Command, args ...string) (runResult, error) {
outBuf := new(strings.Builder)
errBuf := new(strings.Builder)
app.Writer = outBuf
@ -65,7 +67,6 @@ func TestCliCmd(t *testing.T) {
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)"
cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct {
@ -109,12 +110,17 @@ func TestCliCmd(t *testing.T) {
},
}
app := newTestApp(func(ctx *cli.Context) error {
_, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
})
for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) {
defer test.MockProtect(&setting.AppWorkPath)()
defer test.MockProtect(&setting.CustomPath)()
defer test.MockProtect(&setting.CustomConf)()
app := newTestApp(func(_ context.Context, ctx *cli.Command) error {
_, _ = fmt.Fprint(ctx.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
})
for k, v := range c.env {
t.Setenv(k, v)
}
@ -122,34 +128,34 @@ func TestCliCmd(t *testing.T) {
r, err := runTestApp(app, args...)
require.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd+"\n"+r.Stdout)
})
}
}
func TestCliCmdError(t *testing.T) {
app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
app := newTestApp(func(_ context.Context, ctx *cli.Command) error { return errors.New("normal error") })
r, err := runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) })
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return cli.Exit("exit error", 2) })
r, err = runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err)
assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(func(ctx *cli.Context) error { return nil })
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
require.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout)
assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
assert.Empty(t, r.Stdout)
app = newTestApp(func(ctx *cli.Context) error { return nil })
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd")
require.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called

View file

@ -4,30 +4,34 @@
package cmd
import (
"context"
"os"
"time"
"forgejo.org/modules/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdManager represents the manager command
CmdManager = &cli.Command{
// CmdManager represents the manager command
func cmdManager() *cli.Command {
return &cli.Command{
Name: "manager",
Usage: "Manage the running forgejo process",
Description: "This is a command for managing the running forgejo process",
Subcommands: []*cli.Command{
subcmdShutdown,
subcmdRestart,
subcmdReloadTemplates,
subcmdFlushQueues,
subcmdLogging,
subCmdProcesses,
Commands: []*cli.Command{
subcmdShutdown(),
subcmdRestart(),
subcmdReloadTemplates(),
subcmdFlushQueues(),
subcmdLogging(),
subCmdProcesses(),
},
}
subcmdShutdown = &cli.Command{
}
func subcmdShutdown() *cli.Command {
return &cli.Command{
Name: "shutdown",
Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{
@ -37,7 +41,10 @@ var (
},
Action: runShutdown,
}
subcmdRestart = &cli.Command{
}
func subcmdRestart() *cli.Command {
return &cli.Command{
Name: "restart",
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
Flags: []cli.Flag{
@ -47,7 +54,10 @@ var (
},
Action: runRestart,
}
subcmdReloadTemplates = &cli.Command{
}
func subcmdReloadTemplates() *cli.Command {
return &cli.Command{
Name: "reload-templates",
Usage: "Reload template files in the running process",
Flags: []cli.Flag{
@ -57,7 +67,10 @@ var (
},
Action: runReloadTemplates,
}
subcmdFlushQueues = &cli.Command{
}
func subcmdFlushQueues() *cli.Command {
return &cli.Command{
Name: "flush-queues",
Usage: "Flush queues in the running process",
Action: runFlushQueues,
@ -76,7 +89,10 @@ var (
},
},
}
subCmdProcesses = &cli.Command{
}
func subCmdProcesses() *cli.Command {
return &cli.Command{
Name: "processes",
Usage: "Display running processes within the current process",
Action: runProcesses,
@ -106,10 +122,10 @@ var (
},
},
}
)
}
func runShutdown(c *cli.Context) error {
ctx, cancel := installSignals()
func runShutdown(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -117,8 +133,8 @@ func runShutdown(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runRestart(c *cli.Context) error {
ctx, cancel := installSignals()
func runRestart(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -126,8 +142,8 @@ func runRestart(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runReloadTemplates(c *cli.Context) error {
ctx, cancel := installSignals()
func runReloadTemplates(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -135,8 +151,8 @@ func runReloadTemplates(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runFlushQueues(c *cli.Context) error {
ctx, cancel := installSignals()
func runFlushQueues(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -144,8 +160,8 @@ func runFlushQueues(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runProcesses(c *cli.Context) error {
ctx, cancel := installSignals()
func runProcesses(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
@ -11,11 +12,11 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
defaultLoggingFlags = []cli.Flag{
func defaultLoggingFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "logger",
Usage: `Logger name - will default to "default"`,
@ -56,11 +57,13 @@ var (
Name: "debug",
},
}
}
subcmdLogging = &cli.Command{
func subcmdLogging() *cli.Command {
return &cli.Command{
Name: "logging",
Usage: "Adjust logging commands",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
{
Name: "pause",
Usage: "Pause logging (Forgejo will buffer logs up to a certain point and will drop them after that point)",
@ -104,11 +107,11 @@ var (
}, {
Name: "add",
Usage: "Add a logger",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
{
Name: "file",
Usage: "Add a file logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
Flags: append(defaultLoggingFlags(), []cli.Flag{
&cli.StringFlag{
Name: "filename",
Aliases: []string{"f"},
@ -152,7 +155,7 @@ var (
}, {
Name: "conn",
Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
Flags: append(defaultLoggingFlags(), []cli.Flag{
&cli.BoolFlag{
Name: "reconnect-on-message",
Aliases: []string{"R"},
@ -193,10 +196,10 @@ var (
},
},
}
)
}
func runRemoveLogger(c *cli.Context) error {
ctx, cancel := installSignals()
func runRemoveLogger(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -210,8 +213,8 @@ func runRemoveLogger(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runAddConnLogger(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddConnLogger(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -237,11 +240,11 @@ func runAddConnLogger(c *cli.Context) error {
if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
}
return commonAddLogger(c, mode, vals)
return commonAddLogger(ctx, c, mode, vals)
}
func runAddFileLogger(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddFileLogger(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -270,10 +273,10 @@ func runAddFileLogger(c *cli.Context) error {
if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level")
}
return commonAddLogger(c, mode, vals)
return commonAddLogger(ctx, c, mode, vals)
}
func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error {
if len(c.String("level")) > 0 {
vals["level"] = log.LevelFromString(c.String("level")).String()
}
@ -300,15 +303,15 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
if c.IsSet("writer") {
writer = c.String("writer")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
extra := private.AddLogger(ctx, logger, writer, mode, vals)
return handleCliResponseExtra(extra)
}
func runPauseLogging(c *cli.Context) error {
ctx, cancel := installSignals()
func runPauseLogging(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -317,8 +320,8 @@ func runPauseLogging(c *cli.Context) error {
return nil
}
func runResumeLogging(c *cli.Context) error {
ctx, cancel := installSignals()
func runResumeLogging(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -327,8 +330,8 @@ func runResumeLogging(c *cli.Context) error {
return nil
}
func runReleaseReopenLogging(c *cli.Context) error {
ctx, cancel := installSignals()
func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -337,8 +340,8 @@ func runReleaseReopenLogging(c *cli.Context) error {
return nil
}
func runSetLogSQL(c *cli.Context) error {
ctx, cancel := installSignals()
func runSetLogSQL(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)

View file

@ -11,19 +11,21 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdMigrate represents the available migrate sub-command.
var 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.",
Action: runMigrate,
func cmdMigrate() *cli.Command {
return &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.",
Action: runMigrate,
}
}
func runMigrate(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runMigrate(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -22,79 +22,81 @@ import (
"forgejo.org/modules/setting"
"forgejo.org/modules/storage"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"xorm.io/xorm"
)
// CmdMigrateStorage represents the available migrate storage sub-command.
var CmdMigrateStorage = &cli.Command{
Name: "migrate-storage",
Usage: "Migrate the storage",
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
Action: runMigrateStorage,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "type",
Aliases: []string{"t"},
Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'",
func cmdMigrateStorage() *cli.Command {
return &cli.Command{
Name: "migrate-storage",
Usage: "Migrate the storage",
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
Action: runMigrateStorage,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "type",
Aliases: []string{"t"},
Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'",
},
&cli.StringFlag{
Name: "storage",
Aliases: []string{"s"},
Value: "",
Usage: "New storage type: local (default) or minio",
},
&cli.StringFlag{
Name: "path",
Aliases: []string{"p"},
Value: "",
Usage: "New storage placement if store is local (leave blank for default)",
},
&cli.StringFlag{
Name: "minio-endpoint",
Value: "",
Usage: "Minio storage endpoint",
},
&cli.StringFlag{
Name: "minio-access-key-id",
Value: "",
Usage: "Minio storage accessKeyID",
},
&cli.StringFlag{
Name: "minio-secret-access-key",
Value: "",
Usage: "Minio storage secretAccessKey",
},
&cli.StringFlag{
Name: "minio-bucket",
Value: "",
Usage: "Minio storage bucket",
},
&cli.StringFlag{
Name: "minio-location",
Value: "",
Usage: "Minio storage location to create bucket",
},
&cli.StringFlag{
Name: "minio-base-path",
Value: "",
Usage: "Minio storage base path on the bucket",
},
&cli.BoolFlag{
Name: "minio-use-ssl",
Usage: "Enable SSL for minio",
},
&cli.BoolFlag{
Name: "minio-insecure-skip-verify",
Usage: "Skip SSL verification",
},
&cli.StringFlag{
Name: "minio-checksum-algorithm",
Value: "",
Usage: "Minio checksum algorithm (default/md5)",
},
},
&cli.StringFlag{
Name: "storage",
Aliases: []string{"s"},
Value: "",
Usage: "New storage type: local (default) or minio",
},
&cli.StringFlag{
Name: "path",
Aliases: []string{"p"},
Value: "",
Usage: "New storage placement if store is local (leave blank for default)",
},
&cli.StringFlag{
Name: "minio-endpoint",
Value: "",
Usage: "Minio storage endpoint",
},
&cli.StringFlag{
Name: "minio-access-key-id",
Value: "",
Usage: "Minio storage accessKeyID",
},
&cli.StringFlag{
Name: "minio-secret-access-key",
Value: "",
Usage: "Minio storage secretAccessKey",
},
&cli.StringFlag{
Name: "minio-bucket",
Value: "",
Usage: "Minio storage bucket",
},
&cli.StringFlag{
Name: "minio-location",
Value: "",
Usage: "Minio storage location to create bucket",
},
&cli.StringFlag{
Name: "minio-base-path",
Value: "",
Usage: "Minio storage base path on the bucket",
},
&cli.BoolFlag{
Name: "minio-use-ssl",
Usage: "Enable SSL for minio",
},
&cli.BoolFlag{
Name: "minio-insecure-skip-verify",
Usage: "Skip SSL verification",
},
&cli.StringFlag{
Name: "minio-checksum-algorithm",
Value: "",
Usage: "Minio checksum algorithm (default/md5)",
},
},
}
}
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
@ -182,8 +184,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
})
}
func runMigrateStorage(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -4,52 +4,55 @@
package cmd
import (
"context"
"strings"
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdRestoreRepository represents the available restore a repository sub-command.
var CmdRestoreRepository = &cli.Command{
Name: "restore-repo",
Usage: "Restore the repository from disk",
Description: "This is a command for restoring the repository data.",
Action: runRestoreRepository,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "repo_dir",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to restore from",
},
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "Restore destination owner name",
},
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "Restore destination repository name",
},
&cli.StringFlag{
Name: "units",
Value: "",
Usage: `Which items will be restored, one or more units should be separated as comma.
func cmdRestoreRepository() *cli.Command {
return &cli.Command{
Name: "restore-repo",
Usage: "Restore the repository from disk",
Description: "This is a command for restoring the repository data.",
Action: runRestoreRepository,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "repo_dir",
Aliases: []string{"r"},
Value: "./data",
Usage: "Repository dir path to restore from",
},
&cli.StringFlag{
Name: "owner_name",
Value: "",
Usage: "Restore destination owner name",
},
&cli.StringFlag{
Name: "repo_name",
Value: "",
Usage: "Restore destination repository name",
},
&cli.StringFlag{
Name: "units",
Value: "",
Usage: `Which items will be restored, one or more units should be separated as comma.
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
},
&cli.BoolFlag{
Name: "validation",
Usage: "Sanity check the content of the files before trying to load them",
},
},
&cli.BoolFlag{
Name: "validation",
Usage: "Sanity check the content of the files before trying to load them",
},
},
}
}
func runRestoreRepository(c *cli.Context) error {
ctx, cancel := installSignals()
func runRestoreRepository(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setting.MustInstalled()

View file

@ -33,7 +33,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@ -41,20 +41,22 @@ const (
)
// CmdServ represents the available serv sub-command.
var CmdServ = &cli.Command{
Name: "serv",
Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "enable-pprof",
func cmdServ() *cli.Command {
return &cli.Command{
Name: "serv",
Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "enable-pprof",
},
&cli.BoolFlag{
Name: "debug",
},
},
&cli.BoolFlag{
Name: "debug",
},
},
}
}
func setup(ctx context.Context, debug, gitNeeded bool) {
@ -131,8 +133,8 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
return nil
}
func runServ(c *cli.Context) error {
ctx, cancel := installSignals()
func runServ(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
// FIXME: This needs to internationalised
@ -194,7 +196,7 @@ func runServ(c *cli.Context) error {
if git.CheckGitVersionAtLeast("2.29") == nil {
// for AGit Flow
if cmd == "ssh_info" {
fmt.Print(`{"type":"gitea","version":1}`)
fmt.Print(`{"type":"agit","version":1}`)
return nil
}
}

View file

@ -26,48 +26,50 @@ import (
"forgejo.org/routers/install"
"github.com/felixge/fgprof"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// PIDFile could be set from build tag
var PIDFile = "/run/gitea.pid"
// CmdWeb represents the available web sub-command.
var CmdWeb = &cli.Command{
Name: "web",
Usage: "Start the Forgejo web server",
Description: `The Forgejo web server is the only thing you need to run,
func cmdWeb() *cli.Command {
return &cli.Command{
Name: "web",
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),
Action: runWeb,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "port",
Aliases: []string{"p"},
Value: "3000",
Usage: "Temporary port number to prevent conflict",
Before: PrepareConsoleLoggerLevel(log.INFO),
Action: runWeb,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "port",
Aliases: []string{"p"},
Value: "3000",
Usage: "Temporary port number to prevent conflict",
},
&cli.StringFlag{
Name: "install-port",
Value: "3000",
Usage: "Temporary port number to run the install page on to prevent conflict",
},
&cli.StringFlag{
Name: "pid",
Aliases: []string{"P"},
Value: PIDFile,
Usage: "Custom pid file path",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display Fatal logging errors until logging is set-up",
},
&cli.BoolFlag{
Name: "verbose",
Usage: "Set initial logging to TRACE level until logging is properly set-up",
},
},
&cli.StringFlag{
Name: "install-port",
Value: "3000",
Usage: "Temporary port number to run the install page on to prevent conflict",
},
&cli.StringFlag{
Name: "pid",
Aliases: []string{"P"},
Value: PIDFile,
Usage: "Custom pid file path",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display Fatal logging errors until logging is set-up",
},
&cli.BoolFlag{
Name: "verbose",
Usage: "Set initial logging to TRACE level until logging is properly set-up",
},
},
}
}
func runHTTPRedirector() {
@ -128,7 +130,7 @@ func showWebStartupMessage(msg string) {
}
}
func serveInstall(ctx *cli.Context) error {
func serveInstall(_ context.Context, ctx *cli.Command) error {
showWebStartupMessage("Prepare to run install page")
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
@ -161,7 +163,7 @@ func serveInstall(ctx *cli.Context) error {
return nil
}
func serveInstalled(ctx *cli.Context) error {
func serveInstalled(_ context.Context, ctx *cli.Command) error {
setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings()
setting.MustInstalled()
@ -233,7 +235,7 @@ func servePprof() {
finished()
}
func runWeb(ctx *cli.Context) error {
func runWeb(ctx context.Context, cli *cli.Command) error {
defer func() {
if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
@ -251,12 +253,12 @@ func runWeb(ctx *cli.Context) error {
}
// Set pid file setting
if ctx.IsSet("pid") {
createPIDFile(ctx.String("pid"))
if cli.IsSet("pid") {
createPIDFile(cli.String("pid"))
}
if !setting.InstallLock {
if err := serveInstall(ctx); err != nil {
if err := serveInstall(ctx, cli); err != nil {
return err
}
} else {
@ -267,7 +269,7 @@ func runWeb(ctx *cli.Context) error {
go servePprof()
}
return serveInstalled(ctx)
return serveInstalled(ctx, cli)
}
func setPort(port string) error {

View file

@ -1,4 +1,3 @@
#! /bin/bash
# Heavily inspired by https://github.com/urfave/cli
_cli_bash_autocomplete() {
@ -7,9 +6,9 @@ _cli_bash_autocomplete() {
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-shell-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-shell-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0

View file

@ -9,9 +9,9 @@ _cli_zsh_autocomplete() {
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
else
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-shell-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then

View file

@ -1,45 +1,46 @@
Environment To Ini
==================
Multiple docker users have requested that the Gitea docker is changed
to permit arbitrary configuration via environment variables.
This tool allows defining Forgejo's entire configuration via environment
variables, mostly geared towards usage in Docker.
Gitea needs to use an ini file for configuration because the running
environment that starts the docker may not be the same as that used
by the hooks. An ini file also gives a good default and means that
users do not have to completely provide a full environment.
Forgejo needs to use an INI file for configuration because the running
environment that starts the container may not be the same as the one used
by the hooks. An INI file also gives a good default and means that
users do not have to provide the entire set of environment variables.
With those caveats above, this command provides a generic way of
converting suitably structured environment variables into any ini
value.
To use the command is very simple just run it and the default gitea
app.ini will be rewritten to take account of the variables provided,
however there are various options to give slightly different
behavior and these can be interrogated with the `-h` option.
When run, `environment-to-ini` will write the config files based on the
environment variables provided.
Check with the `-h` flag for several options to alter this behaviour.
The environment variables should be of the form:
Environment variables of the form "FORGEJO__SECTION_NAME__KEY_NAME"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value as provided.
GITEA__SECTION_NAME__KEY_NAME
Note, SECTION_NAME in the notation above is case-insensitive.
Environment variables of the form "FORGEJO__SECTION_NAME__KEY_NAME__FILE"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value loaded from the specified file.
Environment variables are usually restricted to a reduced character
set "0-9A-Z_" - in order to allow the setting of sections with
characters outside of that set, they should be escaped as following:
"_0X2E_" for "." and "_0X2D_" for "-". The entire section and key names
can be escaped as a UTF8 byte string if necessary. E.g. to configure:
"_0X2E_" for ".". The entire section and key names can be escaped as
a UTF8 byte string if necessary. E.g. to configure:
"""
...
[log.console]
COLORIZE=false
STDERR=true
...
"""
"""
...
[log.console]
COLORIZE=false
STDERR=true
...
"""
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false"
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
You would set the environment variables: "FORGEJO__LOG_0x2E_CONSOLE__COLORIZE=false"
and "FORGEJO__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
on the configuration cheat sheet.
To build locally, run:

View file

@ -4,16 +4,17 @@
package main
import (
"context"
"os"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func main() {
app := cli.NewApp()
app := cli.Command{}
app.Name = "environment-to-ini"
app.Usage = "Use provided environment to update configuration ini"
app.Description = `As a helper to allow docker users to update the forgejo configuration
@ -72,13 +73,13 @@ func main() {
},
}
app.Action = runEnvironmentToIni
err := app.Run(os.Args)
err := app.Run(context.Background(), os.Args)
if err != nil {
log.Fatal("Failed to run app with %s: %v", os.Args, err)
}
}
func runEnvironmentToIni(c *cli.Context) error {
func runEnvironmentToIni(ctx context.Context, c *cli.Command) error {
// the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...)
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{

View file

@ -408,7 +408,7 @@ local addIssueLabelsOverrides(labels) =
regex: '',
type: 'query',
multi: true,
allValue: '.+'
allValue: '.+',
},
)
.addTemplate(
@ -423,7 +423,7 @@ local addIssueLabelsOverrides(labels) =
regex: '',
type: 'query',
multi: true,
allValue: '.+'
allValue: '.+',
},
)
.addTemplate(

View file

@ -183,7 +183,7 @@ RUN_USER = ; git
;;
;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
;; for system SSH this setting has no effect
;SSH_SERVER_KEY_EXCHANGES = curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
;SSH_SERVER_KEY_EXCHANGES = mlkem768x25519-sha256, curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
;;
;; For the built-in SSH server, choose the MACs to support for SSH connections,
;; for system SSH this setting has no effect
@ -1025,6 +1025,10 @@ LEVEL = Info
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls
;;
;; Comma separated list of default mirror repo units.
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
;DEFAULT_MIRROR_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.wiki,repo.projects,repo.packages
;;
;; Prefix archive files by placing them in a directory named after the repository
;PREFIX_ARCHIVE_FILES = true
;;

View file

@ -1,5 +1,5 @@
import eslintCommunityEslintPluginEslintComments from '@eslint-community/eslint-plugin-eslint-comments';
import stylisticEslintPluginJs from '@stylistic/eslint-plugin-js';
import stylisticEslintPlugin from '@stylistic/eslint-plugin';
import vitest from '@vitest/eslint-plugin';
import arrayFunc from 'eslint-plugin-array-func';
import eslintPluginImportX from 'eslint-plugin-import-x';
@ -26,7 +26,7 @@ export default tseslint.config(
{
plugins: {
'@eslint-community/eslint-comments': eslintCommunityEslintPluginEslintComments,
'@stylistic/js': stylisticEslintPluginJs,
'@stylistic': stylisticEslintPlugin,
'@vitest': vitest,
'array-func': arrayFunc,
'no-jquery': noJquery,
@ -69,62 +69,62 @@ export default tseslint.config(
'@eslint-community/eslint-comments/no-unused-enable': [2],
'@eslint-community/eslint-comments/no-use': [0],
'@eslint-community/eslint-comments/require-description': [0],
'@stylistic/js/array-bracket-newline': [0],
'@stylistic/js/array-bracket-spacing': [2, 'never'],
'@stylistic/js/array-element-newline': [0],
'@stylistic/js/arrow-parens': [2, 'always'],
'@stylistic/array-bracket-newline': [0],
'@stylistic/array-bracket-spacing': [2, 'never'],
'@stylistic/array-element-newline': [0],
'@stylistic/arrow-parens': [2, 'always'],
'@stylistic/js/arrow-spacing': [2, {
'@stylistic/arrow-spacing': [2, {
before: true,
after: true,
}],
'@stylistic/js/block-spacing': [0],
'@stylistic/block-spacing': [0],
'@stylistic/js/brace-style': [2, '1tbs', {
'@stylistic/brace-style': [2, '1tbs', {
allowSingleLine: true,
}],
'@stylistic/js/comma-dangle': [2, 'always-multiline'],
'@stylistic/comma-dangle': [2, 'always-multiline'],
'@stylistic/js/comma-spacing': [2, {
'@stylistic/comma-spacing': [2, {
before: false,
after: true,
}],
'@stylistic/js/comma-style': [2, 'last'],
'@stylistic/js/computed-property-spacing': [2, 'never'],
'@stylistic/js/dot-location': [2, 'property'],
'@stylistic/js/eol-last': [2],
'@stylistic/js/function-call-spacing': [2, 'never'],
'@stylistic/js/function-call-argument-newline': [0],
'@stylistic/js/function-paren-newline': [0],
'@stylistic/js/generator-star-spacing': [0],
'@stylistic/js/implicit-arrow-linebreak': [0],
'@stylistic/comma-style': [2, 'last'],
'@stylistic/computed-property-spacing': [2, 'never'],
'@stylistic/dot-location': [2, 'property'],
'@stylistic/eol-last': [2],
'@stylistic/function-call-spacing': [2, 'never'],
'@stylistic/function-call-argument-newline': [0],
'@stylistic/function-paren-newline': [0],
'@stylistic/generator-star-spacing': [0],
'@stylistic/implicit-arrow-linebreak': [0],
'@stylistic/js/indent': [2, 2, {
'@stylistic/indent': [2, 2, {
ignoreComments: true,
SwitchCase: 1,
}],
'@stylistic/js/key-spacing': [2],
'@stylistic/js/keyword-spacing': [2],
'@stylistic/js/linebreak-style': [2, 'unix'],
'@stylistic/js/lines-around-comment': [0],
'@stylistic/js/lines-between-class-members': [0],
'@stylistic/js/max-len': [0],
'@stylistic/js/max-statements-per-line': [0],
'@stylistic/js/multiline-ternary': [0],
'@stylistic/js/new-parens': [2],
'@stylistic/js/newline-per-chained-call': [0],
'@stylistic/js/no-confusing-arrow': [0],
'@stylistic/js/no-extra-parens': [0],
'@stylistic/js/no-extra-semi': [2],
'@stylistic/js/no-floating-decimal': [0],
'@stylistic/js/no-mixed-operators': [0],
'@stylistic/js/no-mixed-spaces-and-tabs': [2],
'@stylistic/key-spacing': [2],
'@stylistic/keyword-spacing': [2],
'@stylistic/linebreak-style': [2, 'unix'],
'@stylistic/lines-around-comment': [0],
'@stylistic/lines-between-class-members': [0],
'@stylistic/max-len': [0],
'@stylistic/max-statements-per-line': [0],
'@stylistic/multiline-ternary': [0],
'@stylistic/new-parens': [2],
'@stylistic/newline-per-chained-call': [0],
'@stylistic/no-confusing-arrow': [0],
'@stylistic/no-extra-parens': [0],
'@stylistic/no-extra-semi': [2],
'@stylistic/no-floating-decimal': [0],
'@stylistic/no-mixed-operators': [0],
'@stylistic/no-mixed-spaces-and-tabs': [2],
'@stylistic/js/no-multi-spaces': [2, {
'@stylistic/no-multi-spaces': [2, {
ignoreEOLComments: true,
exceptions: {
@ -132,60 +132,60 @@ export default tseslint.config(
},
}],
'@stylistic/js/no-multiple-empty-lines': [2, {
'@stylistic/no-multiple-empty-lines': [2, {
max: 1,
maxEOF: 0,
maxBOF: 0,
}],
'@stylistic/js/no-tabs': [2],
'@stylistic/js/no-trailing-spaces': [2],
'@stylistic/js/no-whitespace-before-property': [2],
'@stylistic/js/nonblock-statement-body-position': [2],
'@stylistic/js/object-curly-newline': [0],
'@stylistic/js/object-curly-spacing': [2, 'never'],
'@stylistic/js/object-property-newline': [0],
'@stylistic/js/one-var-declaration-per-line': [0],
'@stylistic/js/operator-linebreak': [2, 'after'],
'@stylistic/js/padded-blocks': [2, 'never'],
'@stylistic/js/padding-line-between-statements': [0],
'@stylistic/js/quote-props': [0],
'@stylistic/no-tabs': [2],
'@stylistic/no-trailing-spaces': [2],
'@stylistic/no-whitespace-before-property': [2],
'@stylistic/nonblock-statement-body-position': [2],
'@stylistic/object-curly-newline': [0],
'@stylistic/object-curly-spacing': [2, 'never'],
'@stylistic/object-property-newline': [0],
'@stylistic/one-var-declaration-per-line': [0],
'@stylistic/operator-linebreak': [2, 'after'],
'@stylistic/padded-blocks': [2, 'never'],
'@stylistic/padding-line-between-statements': [0],
'@stylistic/quote-props': [0],
'@stylistic/js/quotes': [2, 'single', {
'@stylistic/quotes': [2, 'single', {
avoidEscape: true,
allowTemplateLiterals: true,
}],
'@stylistic/js/rest-spread-spacing': [2, 'never'],
'@stylistic/rest-spread-spacing': [2, 'never'],
'@stylistic/js/semi': [2, 'always', {
'@stylistic/semi': [2, 'always', {
omitLastInOneLineBlock: true,
}],
'@stylistic/js/semi-spacing': [2, {
'@stylistic/semi-spacing': [2, {
before: false,
after: true,
}],
'@stylistic/js/semi-style': [2, 'last'],
'@stylistic/js/space-before-blocks': [2, 'always'],
'@stylistic/semi-style': [2, 'last'],
'@stylistic/space-before-blocks': [2, 'always'],
'@stylistic/js/space-before-function-paren': [2, {
'@stylistic/space-before-function-paren': [2, {
anonymous: 'ignore',
named: 'never',
asyncArrow: 'always',
}],
'@stylistic/js/space-in-parens': [2, 'never'],
'@stylistic/js/space-infix-ops': [2],
'@stylistic/js/space-unary-ops': [2],
'@stylistic/js/spaced-comment': [2, 'always'],
'@stylistic/js/switch-colon-spacing': [2],
'@stylistic/js/template-curly-spacing': [2, 'never'],
'@stylistic/js/template-tag-spacing': [2, 'never'],
'@stylistic/js/wrap-iife': [2, 'inside'],
'@stylistic/js/wrap-regex': [0],
'@stylistic/js/yield-star-spacing': [2, 'after'],
'@stylistic/space-in-parens': [2, 'never'],
'@stylistic/space-infix-ops': [2],
'@stylistic/space-unary-ops': [2],
'@stylistic/spaced-comment': [2, 'always'],
'@stylistic/switch-colon-spacing': [2],
'@stylistic/template-curly-spacing': [2, 'never'],
'@stylistic/template-tag-spacing': [2, 'never'],
'@stylistic/wrap-iife': [2, 'inside'],
'@stylistic/wrap-regex': [0],
'@stylistic/yield-star-spacing': [2, 'after'],
'accessor-pairs': [2],
'array-callback-return': [2, {

6
flake.lock generated
View file

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1733392399,
"narHash": "sha256-kEsTJTUQfQFIJOcLYFt/RvNxIK653ZkTBIs4DG+cBns=",
"lastModified": 1749285348,
"narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "d0797a04b81caeae77bcff10a9dde78bc17f5661",
"rev": "3e3afe5174c561dee0df6f2c2b2236990146329f",
"type": "github"
},
"original": {

View file

@ -3,35 +3,20 @@
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {
nixpkgs,
flake-utils,
...
}:
outputs =
{
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system: let
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# generic
git
git-lfs
gnumake
gnused
gnutar
gzip
# frontend
nodejs_20
# backend
gofumpt
sqlite
go
gopls
];
};
in
{
devShells.default = import ./shell.nix { inherit pkgs; };
formatter = pkgs.nixfmt-rfc-style;
}
);
}

68
go.mod
View file

@ -2,30 +2,31 @@ module forgejo.org
go 1.24
toolchain go1.24.3
toolchain go1.24.4
require (
code.forgejo.org/f3/gof3/v3 v3.10.6
code.forgejo.org/f3/gof3/v3 v3.11.0
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
code.forgejo.org/forgejo/go-rpmutils v1.0.0
code.forgejo.org/forgejo/levelqueue v1.0.0
code.forgejo.org/forgejo/reply v1.0.2
code.forgejo.org/go-chi/binding v1.0.0
code.forgejo.org/go-chi/cache v1.0.0
code.forgejo.org/go-chi/captcha v1.0.1
code.forgejo.org/go-chi/session v1.0.1
code.forgejo.org/go-chi/binding v1.0.1
code.forgejo.org/go-chi/cache v1.0.1
code.forgejo.org/go-chi/captcha v1.0.2
code.forgejo.org/go-chi/session v1.0.2
code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/sdk/gitea v0.21.0
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.17.0
connectrpc.com/connect v1.18.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
github.com/ProtonMail/go-crypto v1.1.6
github.com/ProtonMail/go-crypto v1.3.0
github.com/PuerkitoBio/goquery v1.10.3
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2
github.com/alecthomas/chroma/v2 v2.18.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.1
github.com/blevesearch/bleve/v2 v2.5.2
github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.23.0
github.com/chi-middleware/proxy v1.1.1
@ -40,14 +41,14 @@ require (
github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.0
github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2
github.com/go-git/go-git/v5 v5.13.2
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-openapi/spec v0.21.0
github.com/go-sql-driver/mysql v1.9.1
github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.13.0
github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
@ -75,7 +76,7 @@ require (
github.com/meilisearch/meilisearch-go v0.31.0
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.27
github.com/minio/minio-go/v7 v7.0.91
github.com/minio/minio-go/v7 v7.0.94
github.com/msteinert/pam/v2 v2.1.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.8.0
@ -86,27 +87,25 @@ require (
github.com/prometheus/client_golang v1.21.1
github.com/redis/go-redis/v9 v9.8.0
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0
github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli/v2 v2.27.6
github.com/urfave/cli/v3 v3.3.3
github.com/valyala/fastjson v1.6.4
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.12
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go v0.126.0
go.uber.org/mock v0.5.1
golang.org/x/crypto v0.38.0
gitlab.com/gitlab-org/api/client-go v0.130.1
go.uber.org/mock v0.5.2
golang.org/x/crypto v0.39.0
golang.org/x/image v0.27.0
golang.org/x/net v0.40.0
golang.org/x/net v0.41.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.14.0
golang.org/x/sync v0.15.0
golang.org/x/sys v0.33.0
golang.org/x/text v0.25.0
golang.org/x/text v0.26.0
google.golang.org/protobuf v1.36.4
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
@ -121,7 +120,6 @@ require (
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
@ -146,14 +144,13 @@ require (
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
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.3 // indirect
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.3.8 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
@ -210,6 +207,7 @@ require (
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
@ -221,15 +219,13 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.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
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/assert v1.3.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
@ -237,18 +233,16 @@ 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
golang.org/x/mod v0.24.0 // indirect
golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.31.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.34.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.26.0
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.28.0
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1

128
go.sum
View file

@ -1,13 +1,15 @@
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
code.forgejo.org/f3/gof3/v3 v3.10.6 h1:Ru/Iz+pqM8IPi7atUHE7+q7v3O3DRbYgMFqrFTsO1m8=
code.forgejo.org/f3/gof3/v3 v3.10.6/go.mod h1:K6lQCWQIyN/5rjP/OJL9fMA6fd++satndE20w/I6Kss=
code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFsAE=
code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
code.forgejo.org/forgejo/act v1.26.0 h1:6mTmoaw7d/WpYiw/Pw6AaypxFdgJog5OFi/PMEgEbxs=
code.forgejo.org/forgejo/act v1.26.0/go.mod h1:HFDFrXPrqfM9aH2RCnMiBdo/3ThxDmZjp58InPjGOfo=
code.forgejo.org/forgejo/act v1.28.0 h1:96njNC7C1YNyjWq5OWvLZMF/nw0PMthzIA8Nwbnn7jo=
code.forgejo.org/forgejo/act v1.28.0/go.mod h1:dFuiwAmD5vyrzecysHB2kL/GM3wRpoVPl+WdbCTC8Bs=
code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE=
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M=
code.forgejo.org/forgejo/go-rpmutils v1.0.0/go.mod h1:cg+VbgLXfrDPza9T+kBsMb3TVmmzPN4XseT6gDGLSUk=
code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:RArF5AsF9LH4nEoJxqRxcP5r8hhRfWcId84G82YbqzA=
code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/UfU/XgPpLuE=
@ -16,22 +18,22 @@ code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCd
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
code.forgejo.org/go-chi/binding v1.0.0 h1:EIDJtk9brK7WsT7rvS/D4cxX8XlnhY3LMy8ex1jeHu0=
code.forgejo.org/go-chi/binding v1.0.0/go.mod h1:fWwqaHj0H1/KeCpBqdvKunflq8pYfciEHI5v3UUeE2E=
code.forgejo.org/go-chi/cache v1.0.0 h1:akLfGxNlHcacmtutovNtYFSTMsbdcp5MGjAEsP4pxnE=
code.forgejo.org/go-chi/cache v1.0.0/go.mod h1:OVlZ/TqDYJ+RUJ+R+J+OLxtlyjo3pbjBeK7LAWAB+Vk=
code.forgejo.org/go-chi/captcha v1.0.1 h1:/oe1fvGOpdyyeGijg3oMYNOYLvEovNvp79Y3gLe3qbk=
code.forgejo.org/go-chi/captcha v1.0.1/go.mod h1:6EbjSVVa7WoZFENgwK/hLAJZq+HBXtgRsjnIngILC8Y=
code.forgejo.org/go-chi/session v1.0.1 h1:RNkcJQZJBqlvJoIFXSth87b3kMFZLDBA18VcitD+Z0Y=
code.forgejo.org/go-chi/session v1.0.1/go.mod h1:y69sjS984wc7k4xyu77yNE5HKeSlBoQW8VSGdsK7RAs=
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
code.forgejo.org/go-chi/binding v1.0.1/go.mod h1:oTFFDg/dkwFbmVuusiULB1OlrIJM95cOGK7Nc3GYcoo=
code.forgejo.org/go-chi/cache v1.0.1 h1:w6IsDcPbeEnEYZn7M2HJe3/3/Ehtcw/72VjcVK7+lBw=
code.forgejo.org/go-chi/cache v1.0.1/go.mod h1:K3aQSyRIN4xiuqV1kanfQ6O4ToDpzDpY3bNOyGjFe3U=
code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkAaqZGQ=
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
code.forgejo.org/go-chi/session v1.0.2 h1:pG+AXre9L9VXJmTaADXkmeEPuRalhmBXyv6tG2Rvjcc=
code.forgejo.org/go-chi/session v1.0.2/go.mod h1:HnEGyBny7WPzCiVLP2vzL5ssma+3gCSl/vLpuVNYrqc=
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
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.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk=
connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
@ -46,13 +48,11 @@ github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U
github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
@ -87,8 +87,8 @@ github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCk
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/blevesearch/bleve/v2 v2.5.1 h1:cc/O++W2Hcjp1SU5ETHeE+QYWv2oV88ldYEPowdmg8M=
github.com/blevesearch/bleve/v2 v2.5.1/go.mod h1:9g/wnbWKm9AgXrU8Ecqi+IDdqjUHWymwkQRDg+5tafU=
github.com/blevesearch/bleve/v2 v2.5.2 h1:Ab0r0MODV2C5A6BEL87GqLBySqp/s9xFgceCju6BQk8=
github.com/blevesearch/bleve/v2 v2.5.2/go.mod h1:5Dj6dUQxZM6aqYT3eutTD/GpWKGFSsV8f7LDidFbwXo=
github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y=
github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/geo v0.2.3 h1:K9/vbGI9ehlXdxjxDRJtoAMt7zGAsMIzc6n8zWcwnhg=
@ -121,13 +121,13 @@ github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
github.com/blevesearch/zapx/v16 v16.2.3 h1:7Y0r+a3diEvlazsncexq1qoFOcBd64xwMS7aDm4lo1s=
github.com/blevesearch/zapx/v16 v16.2.3/go.mod h1:wVJ+GtURAaRG9KQAMNYyklq0egV+XJlGcXNCE0OFjjA=
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/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@ -150,10 +150,8 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
@ -215,8 +213,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
@ -247,8 +245,8 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z
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-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
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=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
@ -384,8 +382,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI=
github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
@ -415,8 +411,8 @@ github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5JQc=
github.com/minio/minio-go/v7 v7.0.91/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -459,6 +455,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
@ -497,17 +495,11 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@ -531,12 +523,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@ -545,8 +539,6 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
@ -564,8 +556,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
gitlab.com/gitlab-org/api/client-go v0.126.0 h1:VV5TdkF6pMbEdFGvbR2CwEgJwg6qdg1u3bj5eD2tiWk=
gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE=
gitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc=
gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -573,8 +565,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
@ -591,8 +583,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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
@ -603,8 +595,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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -620,8 +612,8 @@ 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -633,8 +625,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -684,10 +676,10 @@ 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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -695,8 +687,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.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=

View file

@ -21,7 +21,7 @@ import (
_ "forgejo.org/modules/markup/markdown"
_ "forgejo.org/modules/markup/orgmode"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// these flags will be set by the build flags

38
manifest.scm Normal file
View file

@ -0,0 +1,38 @@
;;; Copyright 2025 The Forgejo Authors. All rights reserved.
;;; SPDX-License-Identifier: MIT
;;;
;;; Commentary:
;;;
;;; This is a GNU Guix manifest that can be used to create a
;;; development environment to build and test Forgejo.
;;;
;;; The following is a usage example to create a containerized
;;; environment, with HOME shared for the Go cache and the network
;;; made available to fetch required Go and Node dependencies.
;;;
#|
guix shell -CNF --share=$HOME -m manifest.scm
export GOTOOLCHAIN=local # to use the Go binary from Guix
export CC=gcc CGO_ENABLED=1
export TAGS="timetzdata sqlite sqlite_unlock_notify"
make clean
make -j$(nproc)
make test -j$(nproc) # run unit tests
make test-sqlite -j$(nproc) # run integration tests
make watch # run an instance/rebuild on changes
|#
(specifications->manifest
(list "bash-minimal"
"coreutils"
"findutils"
"gcc-toolchain"
"git" ;libpcre support is required
"git-lfs"
"gnupg"
"go"
"grep"
"make"
"node"
"nss-certs"
"openssh"
"sed"))

View file

@ -5,6 +5,7 @@ package actions
import (
"context"
"errors"
"fmt"
"slices"
"strings"
@ -54,6 +55,7 @@ type ActionRun struct {
PreviousDuration time.Duration
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
NotifyEmail bool
}
func init() {
@ -222,29 +224,38 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
var hasWaiting bool
for _, v := range jobs {
id, job := v.Job()
needs := job.Needs()
if err := v.SetJob(id, job.EraseNeeds()); err != nil {
return err
status := StatusFailure
payload := []byte{}
needs := []string{}
name := run.Title
runsOn := []string{}
if job != nil {
needs = job.Needs()
if err := v.SetJob(id, job.EraseNeeds()); err != nil {
return err
}
payload, _ = v.Marshal()
if len(needs) > 0 || run.NeedApproval {
status = StatusBlocked
} else {
status = StatusWaiting
hasWaiting = true
}
name, _ = util.SplitStringAtByteN(job.Name, 255)
runsOn = job.RunsOn()
}
payload, _ := v.Marshal()
status := StatusWaiting
if len(needs) > 0 || run.NeedApproval {
status = StatusBlocked
} else {
hasWaiting = true
}
job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
runJobs = append(runJobs, &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
IsForkPullRequest: run.IsForkPullRequest,
Name: job.Name,
Name: name,
WorkflowPayload: payload,
JobID: id,
Needs: needs,
RunsOn: job.RunsOn(),
RunsOn: runsOn,
Status: status,
})
}
@ -346,7 +357,7 @@ func UpdateRunWithoutNotification(ctx context.Context, run *ActionRun, cols ...s
return err
}
if affected == 0 {
return fmt.Errorf("run has changed")
return errors.New("run has changed")
// It's impossible that the run is not found, since Gitea never deletes runs.
}

View file

@ -54,6 +54,8 @@ type FindRunJobOptions struct {
CommitSHA string
Statuses []Status
UpdatedBefore timeutil.TimeStamp
Events []string // []webhook_module.HookEventType
RunNumber int64
}
func (opts FindRunJobOptions) ToConds() builder.Cond {
@ -76,5 +78,11 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
if opts.UpdatedBefore > 0 {
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
}
if len(opts.Events) > 0 {
cond = cond.And(builder.In("event", opts.Events))
}
if opts.RunNumber > 0 {
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
}
return cond
}

View file

@ -16,6 +16,7 @@ import (
repo_model "forgejo.org/models/repo"
"forgejo.org/models/shared/types"
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
"forgejo.org/modules/optional"
"forgejo.org/modules/timeutil"
"forgejo.org/modules/translation"
@ -353,3 +354,53 @@ func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
}
return res.RowsAffected()
}
func DeleteOfflineRunners(ctx context.Context, olderThan timeutil.TimeStamp, globalOnly bool) error {
log.Info("Doing: DeleteOfflineRunners")
if olderThan.AsTime().After(timeutil.TimeStampNow().AddDuration(-RunnerOfflineTime).AsTime()) {
return fmt.Errorf("invalid `cron.cleanup_offline_runners.older_than`value: must be at least %q", RunnerOfflineTime)
}
cond := builder.Or(
// never online
builder.And(builder.Eq{"last_online": 0}, builder.Lt{"created": olderThan}),
// was online but offline
builder.And(builder.Gt{"last_online": 0}, builder.Lt{"last_online": olderThan}),
)
if globalOnly {
cond = builder.And(cond, builder.Eq{"owner_id": 0}, builder.Eq{"repo_id": 0})
}
if err := db.Iterate(
ctx,
cond,
func(ctx context.Context, r *ActionRunner) error {
if err := DeleteRunner(ctx, r); err != nil {
return fmt.Errorf("DeleteOfflineRunners: %w", err)
}
lastOnline := r.LastOnline.AsTime()
olderThanTime := olderThan.AsTime()
if !lastOnline.IsZero() && lastOnline.Before(olderThanTime) {
log.Info(
"Deleted runner [ID: %d, Name: %s], last online %s ago",
r.ID, r.Name, olderThanTime.Sub(lastOnline).String(),
)
} else {
log.Info(
"Deleted runner [ID: %d, Name: %s], unused since %s ago",
r.ID, r.Name, olderThanTime.Sub(r.Created.AsTime()).String(),
)
}
return nil
},
); err != nil {
return err
}
log.Info("Finished: DeleteOfflineRunners")
return nil
}

View file

@ -6,10 +6,12 @@ import (
"encoding/binary"
"fmt"
"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"
@ -73,3 +75,68 @@ func TestDeleteRunner(t *testing.T) {
idAsBinary[6], idAsBinary[7])
assert.Equal(t, idAsHexadecimal, after.UUID[19:])
}
func TestDeleteOfflineRunnersRunnerGlobalOnly(t *testing.T) {
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
timeutil.MockSet(baseTime)
defer timeutil.MockUnset()
require.NoError(t, unittest.PrepareTestDatabase())
olderThan := timeutil.TimeStampNow().Add(-timeutil.Hour)
require.NoError(t, DeleteOfflineRunners(db.DefaultContext, olderThan, true))
// create at test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 12345678})
// last_online test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000001})
// created one month ago but a repo
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000002})
// last online one hour ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000003})
// last online 10 seconds ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000004})
// created 1 month ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000005})
// created 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000006})
// last online 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000007})
}
func TestDeleteOfflineRunnersAll(t *testing.T) {
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
timeutil.MockSet(baseTime)
defer timeutil.MockUnset()
require.NoError(t, unittest.PrepareTestDatabase())
olderThan := timeutil.TimeStampNow().Add(-timeutil.Hour)
require.NoError(t, DeleteOfflineRunners(db.DefaultContext, olderThan, false))
// create at test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 12345678})
// last_online test base time
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000001})
// created one month ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000002})
// last online one hour ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000003})
// last online 10 seconds ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000004})
// created 1 month ago
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000005})
// created 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000006})
// last online 1 hour ago
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000007})
}
func TestDeleteOfflineRunnersErrorOnInvalidOlderThanValue(t *testing.T) {
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
timeutil.MockSet(baseTime)
defer timeutil.MockUnset()
require.Error(t, DeleteOfflineRunners(db.DefaultContext, timeutil.TimeStampNow(), false))
}

View file

@ -34,6 +34,15 @@ var statusNames = map[Status]string{
StatusBlocked: "blocked",
}
var nameToStatus = make(map[string]Status, len(statusNames))
func init() {
// Populate name to status lookup map
for status, name := range statusNames {
nameToStatus[name] = status
}
}
// String returns the string name of the Status
func (s Status) String() string {
return statusNames[s]
@ -102,3 +111,8 @@ func (s Status) AsResult() runnerv1.Result {
}
return runnerv1.Result_RESULT_UNSPECIFIED
}
func StatusFromString(name string) (Status, bool) {
status, exists := nameToStatus[name]
return status, exists
}

View file

@ -7,6 +7,7 @@ package activities
import (
"context"
"errors"
"fmt"
"net/url"
"path"
@ -441,6 +442,12 @@ func (a *Action) GetIssueContent(ctx context.Context) string {
return a.Issue.Content
}
func GetActivityByID(ctx context.Context, id int64) (*Action, error) {
var act Action
_, err := db.GetEngine(ctx).ID(id).Get(&act)
return &act, err
}
// GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct {
db.ListOptions
@ -458,7 +465,7 @@ type GetFeedsOptions struct {
// GetFeeds returns actions according to the provided options
func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) {
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
return nil, 0, errors.New("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
}
cond, err := activityQueryCondition(ctx, opts)
@ -466,11 +473,8 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return nil, 0, err
}
sess := db.GetEngine(ctx).Where(cond).
Select("`action`.*"). // this line will avoid select other joined table's columns
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
opts.SetDefaultValues()
sess := db.GetEngine(ctx).Where(cond)
sess = db.SetSessionPagination(sess, &opts)
actions := make([]*Action, 0, opts.PageSize)
@ -597,13 +601,14 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error)
}
// NotifyWatchers creates batch of actions for every watcher.
func NotifyWatchers(ctx context.Context, actions ...*Action) error {
func NotifyWatchers(ctx context.Context, actions ...*Action) ([]Action, error) {
var watchers []*repo_model.Watch
var repo *repo_model.Repository
var err error
var permCode []bool
var permIssue []bool
var permPR []bool
var out []Action
e := db.GetEngine(ctx)
@ -614,14 +619,14 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
// Add feeds for user self and all watchers.
watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
if err != nil {
return fmt.Errorf("get watchers: %w", err)
return nil, fmt.Errorf("get watchers: %w", err)
}
// Be aware that optimizing this correctly into the `GetWatchers` SQL
// query is for most cases less performant than doing this.
blockedDoerUserIDs, err := user_model.ListBlockedByUsersID(ctx, act.ActUserID)
if err != nil {
return fmt.Errorf("user_model.ListBlockedByUsersID: %w", err)
return nil, fmt.Errorf("user_model.ListBlockedByUsersID: %w", err)
}
if len(blockedDoerUserIDs) > 0 {
@ -636,8 +641,9 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
// Add feed for actioner.
act.UserID = act.ActUserID
if _, err = e.Insert(act); err != nil {
return fmt.Errorf("insert new actioner: %w", err)
return nil, fmt.Errorf("insert new actioner: %w", err)
}
out = append(out, *act)
if repoChanged {
act.loadRepo(ctx)
@ -645,7 +651,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
// check repo owner exist.
if err := act.Repo.LoadOwner(ctx); err != nil {
return fmt.Errorf("can't get repo owner: %w", err)
return nil, fmt.Errorf("can't get repo owner: %w", err)
}
} else if act.Repo == nil {
act.Repo = repo
@ -656,7 +662,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
act.ID = 0
act.UserID = act.Repo.Owner.ID
if err = db.Insert(ctx, act); err != nil {
return fmt.Errorf("insert new actioner: %w", err)
return nil, fmt.Errorf("insert new actioner: %w", err)
}
}
@ -709,26 +715,29 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
}
if err = db.Insert(ctx, act); err != nil {
return fmt.Errorf("insert new action: %w", err)
return nil, fmt.Errorf("insert new action: %w", err)
}
}
}
return nil
return out, nil
}
// NotifyWatchersActions creates batch of actions for every watcher.
func NotifyWatchersActions(ctx context.Context, acts []*Action) error {
func NotifyWatchersActions(ctx context.Context, acts []*Action) ([]Action, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
return nil, err
}
defer committer.Close()
var out []Action
for _, act := range acts {
if err := NotifyWatchers(ctx, act); err != nil {
return err
as, err := NotifyWatchers(ctx, act)
if err != nil {
return nil, err
}
out = append(out, as...)
}
return committer.Commit()
return out, committer.Commit()
}
// DeleteIssueActions delete all actions related with issueID

View file

@ -197,7 +197,8 @@ func TestNotifyWatchers(t *testing.T) {
RepoID: 1,
OpType: activities_model.ActionStarRepo,
}
require.NoError(t, activities_model.NotifyWatchers(db.DefaultContext, action))
_, err := activities_model.NotifyWatchers(db.DefaultContext, action)
require.NoError(t, err)
// One watchers are inactive, thus action is only created for user 8, 1, 4, 11
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
@ -226,24 +227,6 @@ func TestNotifyWatchers(t *testing.T) {
})
}
func TestGetFeedsCorrupted(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
ID: 8,
RepoID: 1700,
})
actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
RequestedUser: user,
Actor: user,
IncludePrivate: true,
})
require.NoError(t, err)
assert.Empty(t, actions)
assert.Equal(t, int64(0), count)
}
func TestConsistencyUpdateAction(t *testing.T) {
if !setting.Database.Type.IsSQLite3() {
t.Skip("Test is only for SQLite database.")

View file

@ -0,0 +1,106 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activities
import (
"context"
"fmt"
"forgejo.org/models/db"
user_model "forgejo.org/models/user"
"forgejo.org/modules/json"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
"forgejo.org/modules/validation"
ap "github.com/go-ap/activitypub"
)
type FederatedUserActivity struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
ActorID int64
ActorURI string
Actor *user_model.User `xorm:"-"` // transient
NoteContent string `xorm:"TEXT"`
NoteURL string `xorm:"VARCHAR(255)"`
OriginalNote string `xorm:"TEXT"`
Created timeutil.TimeStamp `xorm:"created"`
}
func init() {
db.RegisterModel(new(FederatedUserActivity))
}
func NewFederatedUserActivity(userID, actorID int64, actorURI, noteContent, noteURL string, originalNote ap.Activity) (FederatedUserActivity, error) {
jsonString, err := json.Marshal(originalNote)
if err != nil {
return FederatedUserActivity{}, err
}
result := FederatedUserActivity{
UserID: userID,
ActorID: actorID,
ActorURI: actorURI,
NoteContent: noteContent,
NoteURL: noteURL,
OriginalNote: string(jsonString),
}
if valid, err := validation.IsValid(result); !valid {
return FederatedUserActivity{}, err
}
return result, nil
}
func (federatedUserActivity FederatedUserActivity) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.UserID, "UserID")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorID, "ActorID")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorURI, "ActorURI")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteContent, "NoteContent")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteURL, "NoteURL")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.OriginalNote, "OriginalNote")...)
return result
}
func CreateUserActivity(ctx context.Context, federatedUserActivity *FederatedUserActivity) error {
if valid, err := validation.IsValid(federatedUserActivity); !valid {
return err
}
_, err := db.GetEngine(ctx).Insert(federatedUserActivity)
return err
}
type GetFollowingFeedsOptions struct {
db.ListOptions
}
func GetFollowingFeeds(ctx context.Context, actorID int64, opts GetFollowingFeedsOptions) ([]*FederatedUserActivity, int64, error) {
log.Debug("user_id = %s", actorID)
sess := db.GetEngine(ctx).Where("user_id = ?", actorID)
opts.SetDefaultValues()
sess = db.SetSessionPagination(sess, &opts)
actions := make([]*FederatedUserActivity, 0, opts.PageSize)
count, err := sess.FindAndCount(&actions)
if err != nil {
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
}
for _, act := range actions {
if err := act.loadActor(ctx); err != nil {
return nil, 0, err
}
}
return actions, count, err
}
func (federatedUserActivity *FederatedUserActivity) loadActor(ctx context.Context) error {
log.Debug("for activity %s", federatedUserActivity)
actorUser, _, err := user_model.GetFederatedUserByUserID(ctx, federatedUserActivity.ActorID)
if err != nil {
return err
}
federatedUserActivity.Actor = actorUser
return nil
}

View file

@ -0,0 +1,24 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activities
import (
"testing"
"forgejo.org/modules/validation"
)
func Test_FederatedUserActivityValidation(t *testing.T) {
sut := FederatedUserActivity{}
sut.UserID = 13
sut.ActorID = 33
sut.ActorURI = "33"
sut.NoteContent = "Any content!"
sut.NoteURL = "https://example.org/note/17"
sut.OriginalNote = "federatedUserActivityNote-17"
if res, _ := validation.IsValid(sut); !res {
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
}
}

View file

@ -5,6 +5,7 @@ package asymkey
import (
"context"
"errors"
"fmt"
"strings"
"time"
@ -209,7 +210,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified
// deleteGPGKey does the actual key deletion
func deleteGPGKey(ctx context.Context, keyID string) (int64, error) {
if keyID == "" {
return 0, fmt.Errorf("empty KeyId forbidden") // Should never happen but just to be sure
return 0, errors.New("empty KeyId forbidden") // Should never happen but just to be sure
}
// Delete imported key
n, err := db.GetEngine(ctx).Where("key_id=?", keyID).Delete(new(GPGKeyImport))

View file

@ -7,6 +7,7 @@ import (
"bytes"
"crypto"
"encoding/base64"
"errors"
"fmt"
"hash"
"io"
@ -75,7 +76,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) {
// Check type
pkey, ok := p.(*packet.PublicKey)
if !ok {
return nil, fmt.Errorf("key is not a public key")
return nil, errors.New("key is not a public key")
}
return pkey, nil
}
@ -122,15 +123,15 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) {
func extractSignature(s string) (*packet.Signature, error) {
r, err := readArmoredSign(strings.NewReader(s))
if err != nil {
return nil, fmt.Errorf("Failed to read signature armor")
return nil, errors.New("Failed to read signature armor")
}
p, err := packet.Read(r)
if err != nil {
return nil, fmt.Errorf("Failed to read signature packet")
return nil, errors.New("Failed to read signature packet")
}
sig, ok := p.(*packet.Signature)
if !ok {
return nil, fmt.Errorf("Packet is not a signature")
return nil, errors.New("Packet is not a signature")
}
return sig, nil
}

View file

@ -6,6 +6,7 @@ package asymkey
import (
"context"
"errors"
"fmt"
"hash"
"strings"
@ -316,7 +317,7 @@ func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, si
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
// Check if key can sign
if !k.CanSign {
return fmt.Errorf("key can not sign")
return errors.New("key can not sign")
}
// Decode key
pkey, err := base64DecPubKey(k.Content)

View file

@ -10,6 +10,7 @@ import (
"encoding/base64"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"math/big"
"os"
@ -93,7 +94,7 @@ func parseKeyString(content string) (string, error) {
block, _ := pem.Decode([]byte(content))
if block == nil {
return "", fmt.Errorf("failed to parse PEM block containing the public key")
return "", errors.New("failed to parse PEM block containing the public key")
}
if strings.Contains(block.Type, "PRIVATE") {
return "", ErrKeyIsPrivate
@ -226,7 +227,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
// The ssh library can parse the key, so next we find out what key exactly we have.
switch pkeyType {
case ssh.KeyAlgoDSA:
case ssh.KeyAlgoDSA: //nolint:staticcheck
rawPub := struct {
Name string
P, Q, G, Y *big.Int

View file

@ -69,6 +69,9 @@ func (l *XORMLogBridge) Warn(v ...any) {
// Warnf show warning log
func (l *XORMLogBridge) Warnf(format string, v ...any) {
if format == "Table %s Column %s db default is %s, struct default is %s" || format == "Table %s Column %s db nullable is %v, struct nullable is %v" {
return
}
l.Log(stackLevel, log.WARN, format, v...)
}

View file

@ -414,7 +414,7 @@ func IsErrSHAOrCommitIDNotProvided(err error) bool {
}
func (err ErrSHAOrCommitIDNotProvided) Error() string {
return "a SHA or commit ID must be proved when updating a file"
return "a SHA or commit ID must be provided when updating a file"
}
// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy

View file

@ -0,0 +1,17 @@
-
id: 1001
run_id: 792
runner_id: 1
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
storage_path: "27/5/1730330775594233150.chunk"
file_size: 693147180559
file_compressed_size: 693147180559
content_encoding: "application/zip"
artifact_path: "big-file.zip"
artifact_name: "big-file"
status: 4
created_unix: 1730330775
updated_unix: 1730330775
expired_unix: 1738106775

View file

@ -59,14 +59,6 @@
created_unix: 1603011540 # grouped with id:7
- id: 8
user_id: 1
op_type: 12 # close issue
act_user_id: 1
repo_id: 1700 # dangling intentional
is_private: false
created_unix: 1603011541
- id: 9
user_id: 34
op_type: 12 # close issue
act_user_id: 34

View file

@ -471,3 +471,64 @@
need_approval: 0
approved_by: 0
event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}'
# GET action run(s) test
-
id: 892
title: "successful push run"
repo_id: 63
owner_id: 2
workflow_id: "success.yaml"
index: 1
trigger_user_id: 2
ref: "refs/heads/main"
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
event: "push"
is_fork_pull_request: 0
status: 1 # success
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 893
title: "failed pull_request run"
repo_id: 63
owner_id: 2
workflow_id: "failed.yaml"
index: 2
trigger_user_id: 2
ref: "refs/heads/bugfix-1"
commit_sha: "35c5cddfc19397501ec8f4f7bb808a7c8f04445f"
event: "pull_request"
is_fork_pull_request: 0
status: 2 # failure
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 894
title: "running workflow_dispatch run"
repo_id: 63
owner_id: 2
workflow_id: "running.yaml"
index: 3
trigger_user_id: 2
ref: "refs/heads/main"
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
event: "workflow_dispatch"
is_fork_pull_request: 0
status: 6 # running
started: 1683636528
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0

View file

@ -18,3 +18,122 @@
created: 1716104432
updated: 1716104432
deleted: ~
- id: 10000001
uuid: 10d3b248-6460-4bf5-b819-1f5b3109e10f
name: global-online
version: v6.3.1+7-gc4c0ca0
owner_id: 0
repo_id: 0
description: ""
base: 0
repo_range: ""
token_hash: 7e9ed71f64e98ce1f70e94c63f3cb6c41a8cb0b90de3e1daf7ec5c35361d60ed44da67c5ac393b7aaf443dcfc766007dc828
token_salt: WUcgZWl7mW
last_online: 1716104422
last_active: 0
agent_labels: '["docker"]'
created: 1716104431
updated: 1716104422
deleted: ~
- id: 10000002
uuid: 1d188484-dd97-4a70-b707-5e87b578ab6b
name: repo-never-used
version: v6.3.1+7-gc4c0ca0
owner_id: 0
repo_id: 1
description: ""
base: 0
repo_range: ""
token_hash: 51e88c17ac8b54dd101dc2e4f530a71643c703adba7170f4b1a28f1cb483b4cfb107798c521e0532ef3c6480b64518a5c6a5
token_salt: 4rh8ncXYIO
last_online: 0
last_active: 0
agent_labels: '["docker"]'
created: 1713512432
updated: 1713512432
deleted: ~
- id: 10000003
uuid: 7a039c6b-b0b2-4cf5-a93d-715d617f99e2
name: global-offline
version: v6.3.1+7-gc4c0ca0
owner_id: 0
repo_id: 0
description: ""
base: 0
repo_range: ""
token_hash: c76960c56bc6069f0d1648991ec626500abe8c15286f5c355d565c3b5ba945d7d6f1272a6c77849e592528179511b94f5d69
token_salt: TFMe2jhOkB
last_online: 1715499632
last_active: 0
agent_labels: '["docker"]'
created: 1715499632
updated: 1715499632
deleted: ~
- id: 10000004
uuid: 93ca7fdd-faca-4df6-a474-8345263ef10b
name: user-online
version: v6.3.1+7-gc4c0ca0
owner_id: 1
repo_id: 0
description: ""
base: 0
repo_range: ""
token_hash: 6ddf7f0f2301d2b3f66418145dc497a6d09fa6586e659afcb5ae2a0c5b639561d795aff8062537db9df73b396842ea826134
token_salt: QcdGuReAp4
last_online: 1716104422
last_active: 0
agent_labels: '["docker"]'
created: 1716104431
updated: 1716104422
deleted: ~
- id: 10000005
uuid: a8534df6-c4be-40f4-9714-903b69d973d9
name: user-never-used
version: v6.3.1+7-gc4c0ca0
owner_id: 1
repo_id: 0
description: desc
base: 0
repo_range: ""
token_hash: 4441de7defcfc3d21baa608dec66a562cf23307abddaabdbb836907ac5f48c8780c354891916c525b79ec7af8e95be7a09b4
token_salt: ONNqIOnj3t
last_online: 0
last_active: 0
agent_labels: '["docker"]'
created: 1713512433
updated: 1713512433
deleted: ~
- id: 10000006
uuid: e1c5bb6c-de68-4335-8955-5192f76708ac
name: orga-fresh-created
version: v6.3.1+7-gc4c0ca0
owner_id: 35
repo_id: 0
description: ""
base: 0
repo_range: ""
token_hash: a61f9ee48c6847d243ace0a8936efe80af9277c7bc46d6da6e03d1d406608b8023ee66600ad24f0effaa8e3338f92ac97ac9
token_salt: fZJKjrFGWA
last_online: 0
last_active: 0
agent_labels: '["docker"]'
created: 1716100832
updated: 1716100832
deleted: ~
- id: 10000007
uuid: ff755f06-948e-479b-8031-5b3e9f123e32
name: orga-offline
version: v6.3.1+7-gc4c0ca0
owner_id: 35
repo_id: 0
description: ""
base: 0
repo_range: ""
token_hash: 9372efb38f9b64efe65065380abe2f24ef34a59d9619f4cdc08f1151e9849f0b6e722aa10538e8730288de6e2f09acdac695
token_salt: TnU7iiIdCb
last_online: 1716100832
last_active: 0
agent_labels: '["docker"]'
created: 1736085520
updated: 1716100832
deleted: ~

View file

@ -113,3 +113,344 @@
review_id: 22
assignee_id: 5
created_unix: 946684817
-
id: 13
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["4ca8bcaf27e28504df7bf996819665986b01c847","96cef4a7b72b3c208340ae6f0cf55a93e9077c93","c5626fc9eff57eb1bb7b796b01d4d0f2f3f792a2"]}'
created_unix: 1688672373
-
id: 14
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["23576dd018294e476c06e569b6b0f170d0558705"]}'
created_unix: 1688672374
-
id: 15
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["3e64625bd6eb5bcba69ac97de6c8f507402df861", "c704db5794097441aa2d9dd834d5b7e2f8f08108"]}'
created_unix: 1688672375
-
id: 16
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["811d46c7e518f4f180afb862c0db5cb8c80529ce", "747ddb3506a4fa04a7747808eb56ae16f9e933dc", "837d5c8125633d7d258f93b998e867eab0145520", "1978192d98bb1b65e11c2cf37da854fbf94bffd6"]}'
created_unix: 1688672376
-
id: 17
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":true,"commit_ids":["1978192d98bb1b65e11c2cf37da854fbf94bffd6", "9b93963cf6de4dc33f915bb67f192d099c301f43"]}'
created_unix: 1749734240
-
id: 2000
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 1
old_milestone_id: 0
created_unix: 946684820
-
id: 2001
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 2
old_milestone_id: 1
created_unix: 946684920
-
id: 2002
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 0
old_milestone_id: 2
created_unix: 946685020
-
id: 2003
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 10 # not exsting milestone
old_milestone_id: 0
created_unix: 946685080
-
id: 2010
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 1
old_project_id: 0
created_unix: 946685120
-
id: 2011
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 2
old_project_id: 1
created_unix: 946685220
-
id: 2012
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 0
old_project_id: 2
created_unix: 946685320
-
id: 2013
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 10 # not existing project
old_project_id: 0
created_unix: 946685420
-
id: 2020
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 1
content: 1 # add label
created_unix: 946685520
-
id: 2021
type: 7 # label
poster_id: 1
issue_id: 1
label_id: 2
content: 1 # add label
created_unix: 946685620
-
id: 2022
type: 7 # label
poster_id: 2
issue_id: 1 # in repo_id 1
label_id: 1
content: 0 # remove label
created_unix: 946685720
-
id: 2023
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 1
content: 1 # add label
created_unix: 946685720
-
id: 2024
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 2
content: 0 # remove label
created_unix: 946685720
-
id: 2025
type: 7 # label
poster_id: 2
issue_id: 1 # in repo_id 1
label_id: 2
content: 1 # add label
created_unix: 946685820
-
id: 2026
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 1
content: 0 # remove label
created_unix: 946685920
-
id: 2027
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 2
content: 0 # remove label
created_unix: 946685920
- id: 2040
type: 9 # assignee
poster_id: 1
issue_id: 1 # in repo_id 1
assignee_id: 1 # self
removed_assignee: false # add assignee
created_unix: 946688020
- id: 2041
type: 9 # assignee
poster_id: 2
issue_id: 1 # in repo_id 1
assignee_id: 1
removed_assignee: true # remove assignee
created_unix: 946688120
- id: 2042
type: 9 # assignee
poster_id: 1
issue_id: 1 # in repo_id 1
assignee_id: 2
removed_assignee: false # add assignee
created_unix: 946688220
- id: 2043
type: 9 # assignee
poster_id: 2
issue_id: 1 # in repo_id 1
assignee_id: 2 # self
removed_assignee: true # remove assignee
created_unix: 946688320
- id: 2050
type: 23 # lock
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688420
- id: 2051
type: 24 # unlock
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688520
- id: 2052
type: 23 # lock
poster_id: 1
issue_id: 1 # in repo_id 1
content: "Too heated"
created_unix: 946688620
- id: 2053
type: 24 # unlock
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688720
- id: 2060
type: 36 # pin
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688820
- id: 2061
type: 37 # unpin
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688920
- id: 2070
type: 2 # close
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946689020
- id: 2071
type: 1 # reopen
poster_id: 2
issue_id: 1 # in repo_id 1
created_unix: 946689220
- id: 2072
type: 2 # close
poster_id: 1
issue_id: 2 # pull in repo_id 1
created_unix: 946689320
- id: 2073
type: 1 # reopen
poster_id: 2
issue_id: 2 # pull in repo_id 1
created_unix: 946689420
- id: 2080
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 1
ref_issue_id: 5 # issue in repo_id 1
created_unix: 946689500
- id: 2081
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 1
ref_issue_id: 2 # pull in repo_id 1
created_unix: 946689600
- id: 2082
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 32
ref_issue_id: 16 # issue in repo_id 32
created_unix: 946689700
- id: 2083
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 10
ref_issue_id: 8 # pull in repo_id 10
created_unix: 946689800
- id: 2090
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 1
ref_issue_id: 1 # issue in repo_id 1
created_unix: 946689900
- id: 2091
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 1
ref_issue_id: 2 # pull in repo_id 1
created_unix: 946690000
- id: 2092
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 32
ref_issue_id: 16 # issue in repo_id 32
created_unix: 946690050
- id: 2093
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 10
ref_issue_id: 8 # pull in repo_id 10
created_unix: 946690100

View file

@ -17,3 +17,13 @@
id: 4
user_id: 31
follow_id: 33
-
id: 5
user_id: 4
follow_id: 8
-
id: 6
user_id: 5
follow_id: 8

View file

@ -0,0 +1 @@
[] # empty

View file

@ -795,3 +795,10 @@
type: 10
config: "{}"
created_unix: 946684810
-
id: 115
repo_id: 63
type: 10
config: "{}"
created_unix: 946684810

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