Merge branch 'forgejo' into upload_with_path_structure
Some checks are pending
Integration tests for the release process / release-simulation (push) Waiting to run

This commit is contained in:
David Rotermund 2025-04-22 21:21:46 +00:00
commit 9dfa38225e
199 changed files with 4771 additions and 1806 deletions

View file

@ -4,12 +4,12 @@
"features": {
// installs nodejs into container
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
"version": "22"
},
"ghcr.io/devcontainers/features/git-lfs:1.2.3": {},
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.12"
"version": "3.13"
},
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
},

View file

@ -47,7 +47,7 @@ jobs:
cat <<'EOF'
${{ toJSON(github) }}
EOF
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.4
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.5
with:
target-branch-pattern: "^backport/(?<target>(v.*))$"
strategy: ort

View file

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

View file

@ -39,17 +39,17 @@ 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
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.2 # 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.31.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # 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@39.222.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
RENOVATE_NPM_PACKAGE ?= renovate@39.252.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
@ -1017,8 +1017,7 @@ generate-gomock:
.PHONY: generate-images
generate-images: | node_modules
npm install --no-save fabric@6 imagemin-zopfli@7
node tools/generate-images.js $(TAGS)
node tools/generate-images.js
.PHONY: generate-manpage
generate-manpage:

File diff suppressed because one or more lines are too long

View file

@ -6,6 +6,7 @@ package cmd
import (
"errors"
"fmt"
"strings"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
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",
@ -157,23 +168,40 @@ func runCreateUser(c *cli.Context) error {
IsRestricted: restricted,
}
var accessTokenName string
var accessTokenScope auth_model.AccessTokenScope
if c.IsSet("access-token") {
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
if accessTokenName == "" {
return errors.New("access-token-name cannot be empty")
}
var err error
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
}
// arguments should be prepared before creating the user & access token, in case there is anything wrong
// create the user
if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err)
}
fmt.Printf("New user '%s' has been successfully created!\n", username)
if c.Bool("access-token") {
t := &auth_model.AccessToken{
Name: "gitea-admin",
UID: u.ID,
}
// create the access token
if accessTokenScope != "" {
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
if err := auth_model.NewAccessToken(ctx, t); err != nil {
return err
}
fmt.Printf("Access token was successfully created... %s\n", t.Token)
}
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil
}

View file

@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
},
&cli.StringFlag{
Name: "scopes",
Value: "",
Usage: "Comma separated list of scopes to apply to access token",
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,
@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") {
return errors.New("You must provide a username to generate a token for")
return errors.New("you must provide a username to generate a token for")
}
ctx, cancel := installSignals()
@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
t.Scope = accessTokenScope
// create the token

View file

@ -54,8 +54,8 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p
}
magic := certmagic.NewDefault()
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
// Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool
if setting.AcmeCARoot != "" {
@ -65,7 +65,8 @@ func runACME(listenAddr string, m http.Handler) error {
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
}
}
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
certmagic.DefaultACME = certmagic.ACMEIssuer{
CA: setting.AcmeURL,
TrustedRoots: certPool,
Email: setting.AcmeEmail,
@ -75,7 +76,11 @@ func runACME(listenAddr string, m http.Handler) error {
ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort,
})
}
magic := certmagic.NewDefault()
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
magic.Issuers = []certmagic.Issuer{myACME}

View file

@ -1163,9 +1163,13 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Signing format that Forgejo should use, openpgp uses GPG and ssh uses OpenSSH.
;FORMAT = openpgp
;;
;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
;; run in the context of the RUN_USER
;; Switch to none to stop signing completely
;; Switch to none to stop signing completely.
;; If `FORMAT` is set to **ssh** this should be set to an absolute path to an public OpenSSH key.
;SIGNING_KEY = default
;;
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
@ -2408,7 +2412,7 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The first locale will be used as the default if user browser's language doesn't match any locale in the list.
;LANGS = en-US,zh-CN,zh-HK,zh-TW,da,de-DE,nds,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg,it-IT,fi-FI,fil,eo,tr-TR,cs-CZ,sl,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Danish,Deutsch,Plattdüütsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Filipino,Esperanto,Türkçe,Čeština,Slovenščina,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Dansk,Deutsch,Plattdüütsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Filipino,Esperanto,Türkçe,Čeština,Slovenščina,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -2440,7 +2444,7 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
;MERMAID_MAX_SOURCE_CHARACTERS = 50000
;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
;FILEPREVIEW_MAX_LINES = 50

View file

@ -31,6 +31,21 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
fi
# In case someone wants to sign the `{keyname}.pub` key by `ssh-keygen -s ca -I identity ...` to
# make use of the ssh-key certificate authority feature (see ssh-keygen CERTIFICATES section),
# the generated key file name is `{keyname}-cert.pub`
if [ -e /data/ssh/ssh_host_ed25519_key-cert.pub ]; then
SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_key-cert.pub"}
fi
if [ -e /data/ssh/ssh_host_rsa_key-cert.pub ]; then
SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_key-cert.pub"}
fi
if [ -e /data/ssh/ssh_host_ecdsa_key-cert.pub ]; then
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"}
fi
if [ -d /etc/ssh ]; then
SSH_PORT=${SSH_PORT:-"22"} \
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \

58
go.mod
View file

@ -25,9 +25,9 @@ require (
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2
github.com/alecthomas/chroma/v2 v2.16.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.4.4
github.com/blevesearch/bleve/v2 v2.5.0
github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.22.2
github.com/caddyserver/certmagic v0.23.0
github.com/chi-middleware/proxy v1.1.1
github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1
@ -36,7 +36,7 @@ require (
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/emersion/go-imap v1.2.1
github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.8.0
github.com/fsnotify/fsnotify v1.9.0
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
@ -67,16 +67,16 @@ require (
github.com/jhillyerd/enmime/v2 v2.1.0
github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.17.11
github.com/klauspost/compress v1.18.0
github.com/klauspost/cpuid/v2 v2.2.10
github.com/lib/pq v1.10.9
github.com/markbates/goth v1.80.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.27
github.com/mattn/go-sqlite3 v1.14.28
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.88
github.com/minio/minio-go/v7 v7.0.90
github.com/msteinert/pam/v2 v2.0.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0
@ -100,14 +100,14 @@ require (
github.com/yuin/goldmark v1.7.8
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.0
golang.org/x/crypto v0.36.0
golang.org/x/image v0.25.0
golang.org/x/net v0.38.0
golang.org/x/oauth2 v0.28.0
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
go.uber.org/mock v0.5.1
golang.org/x/crypto v0.37.0
golang.org/x/image v0.26.0
golang.org/x/net v0.39.0
golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.13.0
golang.org/x/sys v0.32.0
golang.org/x/text v0.24.0
google.golang.org/protobuf v1.36.4
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
@ -134,30 +134,30 @@ require (
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring v1.9.3 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/blevesearch/bleve_index_api v1.1.12 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.7 // indirect
github.com/blevesearch/geo v0.1.20 // indirect
github.com/blevesearch/go-faiss v1.0.24 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.0.10 // indirect
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
github.com/blevesearch/zapx/v15 v15.3.16 // indirect
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.1 // indirect
github.com/blevesearch/zapx/v12 v12.4.1 // indirect
github.com/blevesearch/zapx/v13 v13.4.1 // indirect
github.com/blevesearch/zapx/v14 v14.4.1 // indirect
github.com/blevesearch/zapx/v15 v15.4.1 // indirect
github.com/blevesearch/zapx/v16 v16.2.2 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
@ -214,12 +214,12 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v0.2.3 // indirect
github.com/libdns/libdns v1.0.0-beta.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mholt/acmez/v3 v3.1.1 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
@ -253,7 +253,7 @@ require (
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.3.9 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect

121
go.sum
View file

@ -680,8 +680,8 @@ github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNx
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2 h1:cSXom2MoKJ9KPPw29RoZtHvUETY4F4n/kXl8m9btnQ0=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2/go.mod h1:JitQWJ8JuV4Y87l8VsHiiwhb3cgdyn68mX40s7NT6PA=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
@ -717,46 +717,46 @@ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd3
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
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.4.4 h1:RwwLGjUm54SwyyykbrZs4vc1qjzYic4ZnAnY9TwNl60=
github.com/blevesearch/bleve/v2 v2.4.4/go.mod h1:fa2Eo6DP7JR+dMFpQe+WiZXINKSunh7WBtlDGbolKXk=
github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY=
github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8=
github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4=
github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk=
github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY=
github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w=
github.com/blevesearch/go-faiss v1.0.24 h1:K79IvKjoKHdi7FdiXEsAhxpMuns0x4fM0BO93bW5jLI=
github.com/blevesearch/go-faiss v1.0.24/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 h1:uGvKVvG7zvSxCwcm4/ehBa9cCEuZVE+/zvrSl57QUVY=
github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0=
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o=
github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE=
github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b h1:ju9Az5YgrzCeK3M1QwvZIpxYhChkXp7/L0RhDYsxXoE=
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b/go.mod h1:BlrYNpOu4BvVRslmIG+rLtKhmjIaRhIbG8sb9scGTwI=
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA=
github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs=
github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ=
github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II=
github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE=
github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8=
github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE=
github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms=
github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g=
github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y=
github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk=
github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
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=
@ -769,8 +769,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
github.com/caddyserver/certmagic v0.22.2 h1:qzZURXlrxwR5m25/jpvVeEyJHeJJMvAwe5zlMufOTQk=
github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs=
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -893,8 +893,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -1203,8 +1203,8 @@ github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@ -1228,8 +1228,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
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 v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
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/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
@ -1252,12 +1252,12 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY=
github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g=
github.com/mholt/acmez/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ=
github.com/mholt/acmez/v3 v3.1.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
@ -1268,8 +1268,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.88 h1:v8MoIJjwYxOkehp+eiLIuvXk87P2raUtoU5klrAAshs=
github.com/minio/minio-go/v7 v7.0.88/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg=
github.com/minio/minio-go/v7 v7.0.90 h1:TmSj1083wtAD0kEYTx7a5pFsv3iRYMsOJ6A4crjA1lE=
github.com/minio/minio-go/v7 v7.0.90/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
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=
@ -1454,8 +1454,8 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
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=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
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.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -1491,8 +1491,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.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
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/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=
@ -1514,8 +1514,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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1546,8 +1546,8 @@ golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeap
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -1647,8 +1647,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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1678,8 +1678,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1700,8 +1700,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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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=
@ -1793,8 +1793,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1809,8 +1809,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1831,8 +1831,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -2215,6 +2215,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -185,75 +185,6 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
return err
}
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
// Find all runs in the specified repository, reference, and workflow with non-final status
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
RepoID: repoID,
Ref: ref,
WorkflowID: workflowID,
TriggerEvent: event,
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
})
if err != nil {
return err
}
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
if total == 0 {
return nil
}
// Iterate over each found run and cancel its associated jobs.
for _, run := range runs {
// Find all jobs associated with the current run.
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
RunID: run.ID,
})
if err != nil {
return err
}
// Iterate over each job and attempt to cancel it.
for _, job := range jobs {
// Skip jobs that are already in a terminal state (completed, cancelled, etc.).
status := job.Status
if status.IsDone() {
continue
}
// If the job has no associated task (probably an error), set its status to 'Cancelled' and stop it.
if job.TaskID == 0 {
job.Status = StatusCancelled
job.Stopped = timeutil.TimeStampNow()
// Update the job's status and stopped time in the database.
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
if err != nil {
return err
}
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
if n == 0 {
return fmt.Errorf("job has changed, try again")
}
// Continue with the next job.
continue
}
// If the job has an associated task, try to stop the task, effectively cancelling the job.
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
return err
}
}
}
// Return nil to indicate successful cancellation of all running and waiting jobs.
return nil
}
// InsertRun inserts a run
// The title will be cut off at 255 characters if it's longer than 255 characters.
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {

View file

@ -5,7 +5,6 @@ package actions
import (
"context"
"fmt"
"time"
"forgejo.org/models/db"
@ -119,27 +118,6 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
return committer.Commit()
}
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository, cancelPreviousJobs bool) error {
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
// There is no other place we can do this because the app.ini will be changed manually
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
}
if cancelPreviousJobs {
// cancel running cron jobs of this repository and delete old schedules
if err := CancelPreviousJobs(
ctx,
repo.ID,
repo.DefaultBranch,
"",
webhook_module.HookEventSchedule,
); err != nil {
return fmt.Errorf("CancelPreviousJobs: %v", err)
}
}
return nil
}
type FindScheduleOptions struct {
db.ListOptions
RepoID int64

View file

@ -17,10 +17,8 @@ import (
"forgejo.org/modules/timeutil"
"forgejo.org/modules/util"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/nektos/act/pkg/jobparser"
"google.golang.org/protobuf/types/known/timestamppb"
"xorm.io/builder"
)
@ -337,140 +335,6 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
return err
}
// UpdateTaskByState updates the task by the state.
// It will always update the task if the state is not final, even there is no change.
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.TaskState) (*ActionTask, error) {
stepStates := map[int64]*runnerv1.StepState{}
for _, v := range state.Steps {
stepStates[v.Id] = v
}
ctx, commiter, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer commiter.Close()
e := db.GetEngine(ctx)
task := &ActionTask{}
if has, err := e.ID(state.Id).Get(task); err != nil {
return nil, err
} else if !has {
return nil, util.ErrNotExist
} else if runnerID != task.RunnerID {
return nil, fmt.Errorf("invalid runner for task")
}
if task.Status.IsDone() {
// the state is final, do nothing
return task, nil
}
// state.Result is not unspecified means the task is finished
if state.Result != runnerv1.Result_RESULT_UNSPECIFIED {
task.Status = Status(state.Result)
task.Stopped = timeutil.TimeStamp(state.StoppedAt.AsTime().Unix())
if err := UpdateTask(ctx, task, "status", "stopped"); err != nil {
return nil, err
}
if _, err := UpdateRunJob(ctx, &ActionRunJob{
ID: task.JobID,
Status: task.Status,
Stopped: task.Stopped,
}, nil); err != nil {
return nil, err
}
} else {
// Force update ActionTask.Updated to avoid the task being judged as a zombie task
task.Updated = timeutil.TimeStampNow()
if err := UpdateTask(ctx, task, "updated"); err != nil {
return nil, err
}
}
if err := task.LoadAttributes(ctx); err != nil {
return nil, err
}
for _, step := range task.Steps {
var result runnerv1.Result
if v, ok := stepStates[step.Index]; ok {
result = v.Result
step.LogIndex = v.LogIndex
step.LogLength = v.LogLength
step.Started = convertTimestamp(v.StartedAt)
step.Stopped = convertTimestamp(v.StoppedAt)
}
if result != runnerv1.Result_RESULT_UNSPECIFIED {
step.Status = Status(result)
} else if step.Started != 0 {
step.Status = StatusRunning
}
if _, err := e.ID(step.ID).Update(step); err != nil {
return nil, err
}
}
if err := commiter.Commit(); err != nil {
return nil, err
}
return task, nil
}
func StopTask(ctx context.Context, taskID int64, status Status) error {
if !status.IsDone() {
return fmt.Errorf("cannot stop task with status %v", status)
}
e := db.GetEngine(ctx)
task := &ActionTask{}
if has, err := e.ID(taskID).Get(task); err != nil {
return err
} else if !has {
return util.ErrNotExist
}
if task.Status.IsDone() {
return nil
}
now := timeutil.TimeStampNow()
task.Status = status
task.Stopped = now
if _, err := UpdateRunJob(ctx, &ActionRunJob{
ID: task.JobID,
Status: task.Status,
Stopped: task.Stopped,
}, nil); err != nil {
return err
}
if err := UpdateTask(ctx, task, "status", "stopped"); err != nil {
return err
}
if err := task.LoadAttributes(ctx); err != nil {
return err
}
for _, step := range task.Steps {
if !step.Status.IsDone() {
step.Status = status
if step.Started == 0 {
step.Started = now
}
step.Stopped = now
}
if _, err := e.ID(step.ID).Update(step); err != nil {
return err
}
}
return nil
}
func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, limit int) ([]*ActionTask, error) {
e := db.GetEngine(ctx)
@ -481,13 +345,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
Find(&tasks)
}
func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
return timeutil.TimeStamp(0)
}
return timeutil.TimeStamp(timestamp.AsTime().Unix())
}
func logFileName(repoFullName string, taskID int64) string {
ret := fmt.Sprintf("%s/%02x/%d.log", repoFullName, taskID%256, taskID)

View file

@ -201,7 +201,7 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica
}
}
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
if setting.Repository.Signing.Format == "openpgp" && setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
// OK we should try the default key
gpgSettings := git.GPGSettings{
Sign: true,

View file

@ -12,8 +12,10 @@ import (
"forgejo.org/models/db"
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/42wim/sshsig"
"golang.org/x/crypto/ssh"
)
// ParseObjectWithSSHSignature check if signature is good against keystore.
@ -62,6 +64,22 @@ func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *u
}
}
// If the SSH instance key is set, try to verify it with that key.
if setting.SSHInstanceKey != nil {
instanceSSHKey := &PublicKey{
Content: string(ssh.MarshalAuthorizedKey(setting.SSHInstanceKey)),
Fingerprint: ssh.FingerprintSHA256(setting.SSHInstanceKey),
}
instanceUser := &user_model.User{
Name: setting.Repository.Signing.SigningName,
Email: setting.Repository.Signing.SigningEmail,
}
commitVerification := verifySSHObjectVerification(c.Signature.Signature, c.Signature.Payload, instanceSSHKey, committer, instanceUser, setting.Repository.Signing.SigningEmail)
if commitVerification != nil {
return commitVerification
}
}
return &ObjectVerification{
CommittingUser: committer,
Verified: false,

View file

@ -4,6 +4,7 @@
package asymkey
import (
"os"
"testing"
"forgejo.org/models/db"
@ -15,6 +16,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)
func TestParseCommitWithSSHSignature(t *testing.T) {
@ -150,4 +152,43 @@ muPLbvEduU+Ze/1Ol1pgk=
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
})
t.Run("Instance key", func(t *testing.T) {
pubKeyContent, err := os.ReadFile("../../tests/integration/ssh-signing-key.pub")
require.NoError(t, err)
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(pubKeyContent)
require.NoError(t, err)
defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")()
defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")()
defer test.MockVariableValue(&setting.SSHInstanceKey, pubKey)()
gitCommit := &git.Commit{
Committer: &git.Signature{
Email: "fox@example.com",
},
Signature: &git.ObjectSignature{
Payload: `tree f96f1a4f1a51dc42e2983592f503980b60b8849c
parent 93f84db542dd8c6e952c8130bc2fcbe2e299b8b4
author OwO <instance@example.com> 1738961379 +0100
committer UwU <fox@example.com> 1738961379 +0100
Fox
`,
Signature: `-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgV5ELwZ8XJe2LLR/UTuEu/vsFdb
t7ry0W8hyzz/b1iocAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQCnyMRkWVVNoZxZkvi/ZoknUhs4LNBmEwZs9e9214WIt+mhKfc6BiHoE2qeluR2McD
Y5RzHnA8Ke9wXddEePCQE=
-----END SSH SIGNATURE-----
`,
},
}
o := commitToGitObject(gitCommit)
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
assert.True(t, commitVerification.Verified)
assert.Equal(t, "UwU / SHA256:QttK41r/zMUeAW71b5UgVSb8xGFF/DlZJ6TyADW+uoI", commitVerification.Reason)
assert.Equal(t, "SHA256:QttK41r/zMUeAW71b5UgVSb8xGFF/DlZJ6TyADW+uoI", commitVerification.SigningSSHKey.Fingerprint)
})
}

View file

@ -283,6 +283,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
return bitmap.toScope(), nil
}
func (s AccessTokenScope) HasPermissionScope() bool {
return s != "" && s != AccessTokenScopePublicOnly
}
// PublicOnly checks if this token scope is limited to public resources
func (s AccessTokenScope) PublicOnly() (bool, error) {
bitmap, err := s.parse()

View file

@ -0,0 +1,17 @@
-
id: 1
size: 10
hash_md5: HASHMD5_1
hash_sha1: HASHSHA1_1
hash_sha256: HASHSHA256_1
hash_sha512: HASHSHA512_1
hash_blake2b: HASHBLAKE2B_1
created_unix: 946687980
-
id: 2
size: 20
hash_md5: HASHMD5_2
hash_sha1: HASHSHA1_2
hash_sha256: HASHSHA256_2
hash_sha512: HASHSHA512_2
created_unix: 946687980

View file

@ -1,4 +1,4 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
@ -19,18 +19,22 @@ type FederationHost struct {
ID int64 `xorm:"pk autoincr"`
HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"`
NodeInfo NodeInfo `xorm:"extends NOT NULL"`
HostPort uint16 `xorm:"NOT NULL DEFAULT 443"`
HostSchema string `xorm:"NOT NULL DEFAULT 'https'"`
LatestActivity time.Time `xorm:"NOT NULL"`
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
KeyID sql.NullString `xorm:"key_id UNIQUE"`
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"`
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}
// Factory function for FederationHost. Created struct is asserted to be valid.
func NewFederationHost(nodeInfo NodeInfo, hostFqdn string) (FederationHost, error) {
func NewFederationHost(hostFqdn string, nodeInfo NodeInfo, port uint16, schema string) (FederationHost, error) {
result := FederationHost{
HostFqdn: strings.ToLower(hostFqdn),
NodeInfo: nodeInfo,
HostPort: port,
HostSchema: schema,
}
if valid, err := validation.IsValid(result); !valid {
return FederationHost{}, err
@ -43,6 +47,8 @@ func (host FederationHost) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(host.HostFqdn, "HostFqdn")...)
result = append(result, validation.ValidateMaxLen(host.HostFqdn, 255, "HostFqdn")...)
result = append(result, validation.ValidateNotEmpty(host.HostPort, "HostPort")...)
result = append(result, validation.ValidateNotEmpty(host.HostSchema, "HostSchema")...)
result = append(result, host.NodeInfo.Validate()...)
if host.HostFqdn != strings.ToLower(host.HostFqdn) {
result = append(result, fmt.Sprintf("HostFqdn has to be lower case but was: %v", host.HostFqdn))

View file

@ -6,7 +6,6 @@ package forgefed
import (
"context"
"fmt"
"strings"
"forgejo.org/models/db"
"forgejo.org/modules/validation"
@ -44,8 +43,18 @@ func findFederationHostFromDB(ctx context.Context, searchKey, searchValue string
return host, nil
}
func FindFederationHostByFqdn(ctx context.Context, fqdn string) (*FederationHost, error) {
return findFederationHostFromDB(ctx, "host_fqdn=?", strings.ToLower(fqdn))
func FindFederationHostByFqdnAndPort(ctx context.Context, fqdn string, port uint16) (*FederationHost, error) {
host := new(FederationHost)
has, err := db.GetEngine(ctx).Where("host_fqdn=? AND host_port=?", fqdn, port).Get(host)
if err != nil {
return nil, err
} else if !has {
return nil, nil
}
if res, err := validation.IsValid(host); !res {
return nil, err
}
return host, nil
}
func FindFederationHostByKeyID(ctx context.Context, keyID string) (*FederationHost, error) {

View file

@ -18,6 +18,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo",
},
LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
}
if res, err := validation.IsValid(sut); !res {
t.Errorf("sut should be valid but was %q", err)
@ -29,6 +31,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo",
},
LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
}
if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: HostFqdn empty")
@ -40,6 +44,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo",
},
LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
}
if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: HostFqdn too long (len=256)")
@ -49,6 +55,8 @@ func Test_FederationHostValidation(t *testing.T) {
HostFqdn: "host.do.main",
NodeInfo: NodeInfo{},
LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
}
if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: NodeInfo invalid")
@ -60,6 +68,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo",
},
LatestActivity: time.Now().Add(1 * time.Hour),
HostPort: 443,
HostSchema: "https",
}
if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: Future timestamp")
@ -71,6 +81,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo",
},
LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
}
if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: HostFqdn lower case")

View file

@ -96,6 +96,8 @@ var migrations = []*Migration{
NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser),
// v28 -> v29
NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation),
// v29 -> v30
NewMigration("Migrate `User.NormalizedFederatedURI` column to extract port & schema into FederatedHost", MigrateNormalizedFederatedURI),
}
// GetCurrentDBVersion returns the current Forgejo database version.

View file

@ -8,7 +8,7 @@ import "xorm.io/xorm"
func AddHashBlake2bToPackageBlob(x *xorm.Engine) error {
type PackageBlob struct {
ID int64 `xorm:"pk autoincr"`
HashBlake2b string
HashBlake2b string `xorm:"hash_blake2b char(128) UNIQUE(blake2b) INDEX"`
}
return x.Sync(&PackageBlob{})
}

View file

@ -0,0 +1,106 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations //nolint:revive
import (
"time"
"forgejo.org/models/migrations/base"
"forgejo.org/modules/forgefed"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
"xorm.io/xorm"
)
func MigrateNormalizedFederatedURI(x *xorm.Engine) error {
// Update schema
type FederatedUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
NormalizedOriginalURL string
}
type User struct {
ID int64 `xorm:"pk autoincr"`
NormalizedFederatedURI string
}
type FederationHost struct {
ID int64 `xorm:"pk autoincr"`
HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"`
NodeInfo NodeInfo `xorm:"extends NOT NULL"`
HostPort uint16 `xorm:"NOT NULL DEFAULT 443"`
HostSchema string `xorm:"NOT NULL DEFAULT 'https'"`
LatestActivity time.Time `xorm:"NOT NULL"`
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}
if err := x.Sync(new(User), new(FederatedUser), new(FederationHost)); err != nil {
return err
}
// Migrate
sessMigration := x.NewSession()
defer sessMigration.Close()
if err := sessMigration.Begin(); err != nil {
return err
}
federatedUsers := make([]*FederatedUser, 0)
err := sessMigration.OrderBy("id").Find(&federatedUsers)
if err != nil {
return err
}
for _, federatedUser := range federatedUsers {
if federatedUser.NormalizedOriginalURL != "" {
log.Trace("migration[30]: FederatedUser was already migrated %v", federatedUser)
} else {
user := &User{}
has, err := sessMigration.Where("id=?", federatedUser.UserID).Get(user)
if err != nil {
return err
}
if !has {
log.Debug("migration[30]: User missing for federated user: %v", federatedUser)
_, err := sessMigration.Delete(federatedUser)
if err != nil {
return err
}
} else {
// Migrate User.NormalizedFederatedURI -> FederatedUser.NormalizedOriginalUrl
sql := "UPDATE `federated_user` SET `normalized_original_url` = ? WHERE `id` = ?"
if _, err := sessMigration.Exec(sql, user.NormalizedFederatedURI, federatedUser.FederationHostID); err != nil {
return err
}
// Migrate (Port, Schema) FederatedUser.NormalizedOriginalUrl -> FederationHost.(Port, Schema)
actorID, err := forgefed.NewActorID(user.NormalizedFederatedURI)
if err != nil {
return err
}
sql = "UPDATE `federation_host` SET `host_port` = ?, `host_schema` = ? WHERE `id` = ?"
if _, err := sessMigration.Exec(sql, actorID.HostPort, actorID.HostSchema, federatedUser.FederationHostID); err != nil {
return err
}
}
}
}
if err := sessMigration.Commit(); err != nil {
return err
}
// Drop User.NormalizedFederatedURI field in extra transaction
sessSchema := x.NewSession()
defer sessSchema.Close()
if err := sessSchema.Begin(); err != nil {
return err
}
if err := base.DropTableColumns(sessSchema, "user", "normalized_federated_uri"); err != nil {
return err
}
return sessSchema.Commit()
}

View file

@ -0,0 +1,81 @@
// Copyright 2025 The Forgejo Authors.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations //nolint:revive
import (
"testing"
"time"
migration_tests "forgejo.org/models/migrations/test"
"forgejo.org/modules/timeutil"
"github.com/stretchr/testify/require"
"xorm.io/xorm/schemas"
)
func Test_MigrateNormalizedFederatedURI(t *testing.T) {
// Old structs
type User struct {
ID int64 `xorm:"pk autoincr"`
NormalizedFederatedURI string
}
type FederatedUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
}
type FederationHost struct {
ID int64 `xorm:"pk autoincr"`
HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"`
SoftwareName string `xorm:"NOT NULL"`
LatestActivity time.Time `xorm:"NOT NULL"`
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}
// Prepare TestEnv
x, deferable := migration_tests.PrepareTestEnv(t, 0,
new(User),
new(FederatedUser),
new(FederationHost),
)
defer deferable()
if x == nil || t.Failed() {
return
}
// test for expected results
getColumn := func(tn, co string) *schemas.Column {
tables, err := x.DBMetas()
require.NoError(t, err)
var table *schemas.Table
for _, elem := range tables {
if elem.Name == tn {
table = elem
break
}
}
return table.GetColumn(co)
}
require.NotNil(t, getColumn("user", "normalized_federated_uri"))
require.Nil(t, getColumn("federation_host", "host_port"))
require.Nil(t, getColumn("federation_host", "host_schema"))
cnt1, err := x.Table("federated_user").Count()
require.NoError(t, err)
require.Equal(t, int64(2), cnt1)
require.NoError(t, MigrateNormalizedFederatedURI(x))
require.Nil(t, getColumn("user", "normalized_federated_uri"))
require.NotNil(t, getColumn("federation_host", "host_port"))
require.NotNil(t, getColumn("federation_host", "host_schema"))
cnt2, err := x.Table("federated_user").Count()
require.NoError(t, err)
require.Equal(t, int64(1), cnt2)
// idempotent
require.NoError(t, MigrateNormalizedFederatedURI(x))
}

View file

@ -0,0 +1,10 @@
-
id: 1
user_id: 3
federation_host_id: 1
external_id: "18"
-
id: 2
user_id: 999
federation_host_id: 1
external_id: "19"

View file

@ -0,0 +1,5 @@
-
id: 1
host_fqdn: "my.host.x"
software_name: forgejo
latest_activity: 2024-04-26 14:14:50

View file

@ -0,0 +1,3 @@
-
id: 3
normalized_federated_uri: "https://my.host.x/api/activitypub/user-id/18"

View file

@ -0,0 +1,31 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package packages
import (
"path/filepath"
"testing"
"forgejo.org/models/unittest"
"forgejo.org/modules/setting"
_ "forgejo.org/models"
_ "forgejo.org/models/actions"
_ "forgejo.org/models/activities"
_ "forgejo.org/models/forgefed"
)
func AddFixtures(dirs ...string) func() {
return unittest.OverrideFixtures(
unittest.FixturesOptions{
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
Base: setting.AppWorkPath,
Dirs: dirs,
},
)
}
func TestMain(m *testing.M) {
unittest.MainTest(m)
}

View file

@ -44,14 +44,19 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
existing := &PackageBlob{}
has, err := e.Where(builder.Eq{
has, err := e.Where(builder.And(
builder.Eq{
"size": pb.Size,
"hash_md5": pb.HashMD5,
"hash_sha1": pb.HashSHA1,
"hash_sha256": pb.HashSHA256,
"hash_sha512": pb.HashSHA512,
"hash_blake2b": pb.HashBlake2b,
}).Get(existing)
},
builder.Or(
builder.Eq{"hash_blake2b": pb.HashBlake2b},
builder.IsNull{"hash_blake2b"},
),
)).Get(existing)
if err != nil {
return nil, false, err
}

View file

@ -0,0 +1,65 @@
// Copyright 2025 The Forgejo Authors.
// SPDX-License-Identifier: GPL-3.0-or-later
package packages
import (
"testing"
"forgejo.org/models/unittest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPackagesGetOrInsertBlob(t *testing.T) {
defer AddFixtures("models/fixtures/TestPackagesGetOrInsertBlob/")()
require.NoError(t, unittest.PrepareTestDatabase())
blake2bIsSet := unittest.AssertExistsAndLoadBean(t, &PackageBlob{ID: 1})
blake2bNotSet := unittest.AssertExistsAndLoadBean(t, &PackageBlob{ID: 2})
var blake2bSetToRandom PackageBlob
blake2bSetToRandom = *blake2bNotSet
blake2bSetToRandom.HashBlake2b = "SOMETHING RANDOM"
for _, testCase := range []struct {
name string
exists bool
packageBlob *PackageBlob
}{
{
name: "exists and blake2b is not null in the database",
exists: true,
packageBlob: blake2bIsSet,
},
{
name: "exists and blake2b is null in the database",
exists: true,
packageBlob: &blake2bSetToRandom,
},
{
name: "does not exists",
exists: false,
packageBlob: &PackageBlob{
Size: 30,
HashMD5: "HASHMD5_3",
HashSHA1: "HASHSHA1_3",
HashSHA256: "HASHSHA256_3",
HashSHA512: "HASHSHA512_3",
HashBlake2b: "HASHBLAKE2B_3",
},
},
} {
t.Run(testCase.name, func(t *testing.T) {
found, has, _ := GetOrInsertBlob(t.Context(), testCase.packageBlob)
assert.Equal(t, testCase.exists, has)
require.NotNil(t, found)
if testCase.exists {
assert.Equal(t, found.ID, testCase.packageBlob.ID)
} else {
unittest.BeanExists(t, &PackageBlob{ID: found.ID})
}
})
}
}

View file

@ -13,18 +13,9 @@ import (
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
_ "forgejo.org/models"
_ "forgejo.org/models/actions"
_ "forgejo.org/models/activities"
_ "forgejo.org/models/forgefed"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
func prepareExamplePackage(t *testing.T) *packages_model.Package {
require.NoError(t, unittest.PrepareTestDatabase())

View file

@ -4,13 +4,10 @@
package user
import (
"context"
"fmt"
"net/url"
"forgejo.org/models/db"
"forgejo.org/modules/setting"
"forgejo.org/modules/validation"
)
// APActorID returns the IRI to the api endpoint of the user
@ -26,19 +23,3 @@ func (u *User) APActorID() string {
func (u *User) APActorKeyID() string {
return u.APActorID() + "#main-key"
}
func GetUserByFederatedURI(ctx context.Context, federatedURI string) (*User, error) {
user := new(User)
has, err := db.GetEngine(ctx).Where("normalized_federated_uri=?", federatedURI).Get(user)
if err != nil {
return nil, err
} else if !has {
return nil, nil
}
if res, err := validation.IsValid(*user); !res {
return nil, err
}
return user, nil
}

View file

@ -62,7 +62,7 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
if u.IsGhost() {
if u.IsGhost() || u.ID <= 0 {
return avatars.DefaultAvatarLink()
}

View file

@ -1,13 +1,11 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"context"
"database/sql"
"forgejo.org/models/db"
"forgejo.org/modules/validation"
)
@ -18,13 +16,15 @@ type FederatedUser struct {
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
KeyID sql.NullString `xorm:"key_id UNIQUE"`
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"`
NormalizedOriginalURL string // This field ist just to keep original information. Pls. do not use for search or as ID!
}
func NewFederatedUser(userID int64, externalID string, federationHostID int64) (FederatedUser, error) {
func NewFederatedUser(userID int64, externalID string, federationHostID int64, normalizedOriginalURL string) (FederatedUser, error) {
result := FederatedUser{
UserID: userID,
ExternalID: externalID,
FederationHostID: federationHostID,
NormalizedOriginalURL: normalizedOriginalURL,
}
if valid, err := validation.IsValid(result); !valid {
return FederatedUser{}, err
@ -32,30 +32,6 @@ func NewFederatedUser(userID int64, externalID string, federationHostID int64) (
return result, nil
}
func getFederatedUserFromDB(ctx context.Context, searchKey, searchValue any) (*FederatedUser, error) {
federatedUser := new(FederatedUser)
has, err := db.GetEngine(ctx).Where(searchKey, searchValue).Get(federatedUser)
if err != nil {
return nil, err
} else if !has {
return nil, nil
}
if res, err := validation.IsValid(*federatedUser); !res {
return nil, err
}
return federatedUser, nil
}
func GetFederatedUserByKeyID(ctx context.Context, keyID string) (*FederatedUser, error) {
return getFederatedUserFromDB(ctx, "key_id=?", keyID)
}
func GetFederatedUserByUserID(ctx context.Context, userID int64) (*FederatedUser, error) {
return getFederatedUserFromDB(ctx, "user_id=?", userID)
}
func (user FederatedUser) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(user.UserID, "UserID")...)

View file

@ -1,6 +1,6 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
@ -135,9 +135,6 @@ type User struct {
AvatarEmail string `xorm:"NOT NULL"`
UseCustomAvatar bool
// For federation
NormalizedFederatedURI string
// Counters
NumFollowers int
NumFollowing int `xorm:"NOT NULL DEFAULT 0"`

View file

@ -50,9 +50,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat
return committer.Commit()
}
func FindFederatedUser(ctx context.Context, externalID string,
federationHostID int64,
) (*User, *FederatedUser, error) {
func FindFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) {
federatedUser := new(FederatedUser)
user := new(User)
has, err := db.GetEngine(ctx).Where("external_id=? and federation_host_id=?", externalID, federationHostID).Get(federatedUser)
@ -77,6 +75,32 @@ func FindFederatedUser(ctx context.Context, externalID string,
return user, federatedUser, nil
}
func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *FederatedUser, error) {
federatedUser := new(FederatedUser)
user := new(User)
has, err := db.GetEngine(ctx).Where("key_id=?", keyID).Get(federatedUser)
if err != nil {
return nil, nil, err
} else if !has {
return nil, nil, nil
}
has, err = db.GetEngine(ctx).ID(federatedUser.UserID).Get(user)
if err != nil {
return nil, nil, err
} else if !has {
return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID)
}
if res, err := validation.IsValid(*user); !res {
return nil, nil, err
}
if res, err := validation.IsValid(*federatedUser); !res {
return nil, nil, err
}
return user, federatedUser, nil
}
func DeleteFederatedUser(ctx context.Context, userID int64) error {
_, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID})
return err

View file

@ -91,7 +91,7 @@ func TestWebhook_EventsArray(t *testing.T) {
func TestCreateWebhook(t *testing.T) {
hook := &Webhook{
RepoID: 3,
URL: "www.example.com/unit_test",
URL: "https://www.example.com/unit_test",
ContentType: ContentTypeJSON,
Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`,
}

View file

@ -1,4 +1,4 @@
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
@ -6,6 +6,7 @@ package forgefed
import (
"fmt"
"net/url"
"strconv"
"strings"
"forgejo.org/modules/validation"
@ -17,11 +18,12 @@ import (
type ActorID struct {
ID string
Source string
Schema string
HostSchema string
Path string
Host string
Port string
HostPort uint16
UnvalidatedInput string
IsPortSupplemented bool
}
// Factory function for ActorID. Created struct is asserted to be valid
@ -40,20 +42,23 @@ func NewActorID(uri string) (ActorID, error) {
func (id ActorID) AsURI() string {
var result string
if id.Port == "" {
result = fmt.Sprintf("%s://%s/%s/%s", id.Schema, id.Host, id.Path, id.ID)
if id.IsPortSupplemented {
result = fmt.Sprintf("%s://%s/%s/%s", id.HostSchema, id.Host, id.Path, id.ID)
} else {
result = fmt.Sprintf("%s://%s:%s/%s/%s", id.Schema, id.Host, id.Port, id.Path, id.ID)
result = fmt.Sprintf("%s://%s:%d/%s/%s", id.HostSchema, id.Host, id.HostPort, id.Path, id.ID)
}
return result
}
func (id ActorID) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...)
result = append(result, validation.ValidateNotEmpty(id.Schema, "schema")...)
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
result = append(result, validation.ValidateNotEmpty(id.Host, "host")...)
result = append(result, validation.ValidateNotEmpty(id.HostPort, "hostPort")...)
result = append(result, validation.ValidateNotEmpty(id.HostSchema, "hostSchema")...)
result = append(result, validation.ValidateNotEmpty(id.UnvalidatedInput, "unvalidatedInput")...)
if id.UnvalidatedInput != id.AsURI() {
@ -104,12 +109,14 @@ func (id PersonID) Validate() []string {
result := id.ActorID.Validate()
result = append(result, validation.ValidateNotEmpty(id.Source, "source")...)
result = append(result, validation.ValidateOneOf(id.Source, []any{"forgejo", "gitea"}, "Source")...)
switch id.Source {
case "forgejo", "gitea":
if strings.ToLower(id.Path) != "api/v1/activitypub/user-id" && strings.ToLower(id.Path) != "api/activitypub/user-id" {
result = append(result, fmt.Sprintf("path: %q has to be a person specific api path", id.Path))
}
}
return result
}
@ -168,6 +175,8 @@ func removeEmptyStrings(ls []string) []string {
return rs
}
// ----------------------------- newActorID --------------------------------------------
func newActorID(uri string) (ActorID, error) {
validatedURI, err := url.ParseRequestURI(uri)
if err != nil {
@ -179,15 +188,27 @@ func newActorID(uri string) (ActorID, error) {
}
length := len(pathWithActorID)
pathWithoutActorID := strings.Join(pathWithActorID[0:length-1], "/")
id := pathWithActorID[length-1]
id := strings.ToLower(pathWithActorID[length-1])
result := ActorID{}
result.ID = id
result.Schema = validatedURI.Scheme
result.Host = validatedURI.Hostname()
result.Path = pathWithoutActorID
result.Port = validatedURI.Port()
result.UnvalidatedInput = uri
result.HostSchema = strings.ToLower(validatedURI.Scheme)
result.Host = strings.ToLower(validatedURI.Hostname())
result.Path = strings.ToLower(pathWithoutActorID)
if validatedURI.Port() == "" && result.HostSchema == "https" {
result.IsPortSupplemented = true
result.HostPort = 443
} else if validatedURI.Port() == "" && result.HostSchema == "http" {
result.IsPortSupplemented = true
result.HostPort = 80
} else {
numPort, _ := strconv.ParseUint(validatedURI.Port(), 10, 16)
result.HostPort = uint16(numPort)
}
result.UnvalidatedInput = strings.ToLower(uri)
return result, nil
}

View file

@ -1,4 +1,4 @@
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
@ -18,11 +18,13 @@ func TestNewPersonId(t *testing.T) {
expected := PersonID{}
expected.ID = "1"
expected.Source = "forgejo"
expected.Schema = "https"
expected.HostSchema = "https"
expected.Path = "api/v1/activitypub/user-id"
expected.Host = "an.other.host"
expected.Port = ""
expected.HostPort = 443
expected.IsPortSupplemented = true
expected.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
sut, _ := NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
@ -31,15 +33,47 @@ func TestNewPersonId(t *testing.T) {
expected = PersonID{}
expected.ID = "1"
expected.Source = "forgejo"
expected.Schema = "https"
expected.HostSchema = "https"
expected.Path = "api/v1/activitypub/user-id"
expected.Host = "an.other.host"
expected.Port = "443"
expected.HostPort = 443
expected.IsPortSupplemented = false
expected.UnvalidatedInput = "https://an.other.host:443/api/v1/activitypub/user-id/1"
sut, _ = NewPersonID("https://an.other.host:443/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
}
expected = PersonID{}
expected.ID = "1"
expected.Source = "forgejo"
expected.HostSchema = "http"
expected.Path = "api/v1/activitypub/user-id"
expected.Host = "an.other.host"
expected.HostPort = 80
expected.IsPortSupplemented = false
expected.UnvalidatedInput = "http://an.other.host:80/api/v1/activitypub/user-id/1"
sut, _ = NewPersonID("http://an.other.host:80/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
}
expected = PersonID{}
expected.ID = "1"
expected.Source = "forgejo"
expected.HostSchema = "https"
expected.Path = "api/v1/activitypub/user-id"
expected.Host = "an.other.host"
expected.HostPort = 443
expected.IsPortSupplemented = false
expected.UnvalidatedInput = "https://an.other.host:443/api/v1/activitypub/user-id/1"
sut, _ = NewPersonID("HTTPS://an.other.host:443/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
}
}
func TestNewRepositoryId(t *testing.T) {
@ -47,10 +81,11 @@ func TestNewRepositoryId(t *testing.T) {
expected := RepositoryID{}
expected.ID = "1"
expected.Source = "forgejo"
expected.Schema = "http"
expected.HostSchema = "http"
expected.Path = "api/activitypub/repository-id"
expected.Host = "localhost"
expected.Port = "3000"
expected.HostPort = 3000
expected.IsPortSupplemented = false
expected.UnvalidatedInput = "http://localhost:3000/api/activitypub/repository-id/1"
sut, _ := NewRepositoryID("http://localhost:3000/api/activitypub/repository-id/1", "forgejo")
if sut != expected {
@ -61,10 +96,11 @@ func TestNewRepositoryId(t *testing.T) {
func TestActorIdValidation(t *testing.T) {
sut := ActorID{}
sut.Source = "forgejo"
sut.Schema = "https"
sut.HostSchema = "https"
sut.Path = "api/v1/activitypub/user-id"
sut.Host = "an.other.host"
sut.Port = ""
sut.HostPort = 443
sut.IsPortSupplemented = true
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/"
if sut.Validate()[0] != "userId should not be empty" {
t.Errorf("validation error expected but was: %v\n", sut.Validate())
@ -73,10 +109,11 @@ func TestActorIdValidation(t *testing.T) {
sut = ActorID{}
sut.ID = "1"
sut.Source = "forgejo"
sut.Schema = "https"
sut.HostSchema = "https"
sut.Path = "api/v1/activitypub/user-id"
sut.Host = "an.other.host"
sut.Port = ""
sut.HostPort = 443
sut.IsPortSupplemented = true
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1?illegal=action"
if sut.Validate()[0] != "not all input was parsed, \nUnvalidated Input:\"https://an.other.host/api/v1/activitypub/user-id/1?illegal=action\" \nParsed URI: \"https://an.other.host/api/v1/activitypub/user-id/1\"" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
@ -87,10 +124,11 @@ func TestPersonIdValidation(t *testing.T) {
sut := PersonID{}
sut.ID = "1"
sut.Source = "forgejo"
sut.Schema = "https"
sut.HostSchema = "https"
sut.Path = "path"
sut.Host = "an.other.host"
sut.Port = ""
sut.HostPort = 443
sut.IsPortSupplemented = true
sut.UnvalidatedInput = "https://an.other.host/path/1"
_, err := validation.IsValid(sut)
@ -101,10 +139,11 @@ func TestPersonIdValidation(t *testing.T) {
sut = PersonID{}
sut.ID = "1"
sut.Source = "forgejox"
sut.Schema = "https"
sut.HostSchema = "https"
sut.Path = "api/v1/activitypub/user-id"
sut.Host = "an.other.host"
sut.Port = ""
sut.HostPort = 443
sut.IsPortSupplemented = true
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
if sut.Validate()[0] != "Value forgejox is not contained in allowed values [forgejo gitea]" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
@ -125,12 +164,10 @@ func TestWebfingerId(t *testing.T) {
func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
var err any
// TODO: remove after test
//_, err = NewPersonId("", "forgejo")
//if err == nil {
// t.Errorf("empty input should be invalid.")
//}
_, err = NewPersonID("", "forgejo")
if err == nil {
t.Errorf("empty input should be invalid.")
}
_, err = NewPersonID("http://localhost:3000/api/v1/something", "forgejo")
if err == nil {
t.Errorf("localhost uris are not external")
@ -155,7 +192,6 @@ func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
if err == nil {
t.Errorf("uri may not contain unparsed elements")
}
_, err = NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
if err != nil {
t.Errorf("this uri should be valid but was: %v", err)

View file

@ -1,4 +1,4 @@
// Copyright 2023 The Forgejo Authors. All rights reserved.
// Copyright 2023, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgefed
@ -10,10 +10,10 @@ import (
func (id ActorID) AsWellKnownNodeInfoURI() string {
wellKnownPath := ".well-known/nodeinfo"
var result string
if id.Port == "" {
result = fmt.Sprintf("%s://%s/%s", id.Schema, id.Host, wellKnownPath)
if id.HostPort == 0 {
result = fmt.Sprintf("%s://%s/%s", id.HostSchema, id.Host, wellKnownPath)
} else {
result = fmt.Sprintf("%s://%s:%s/%s", id.Schema, id.Host, id.Port, wellKnownPath)
result = fmt.Sprintf("%s://%s:%d/%s", id.HostSchema, id.Host, id.HostPort, wellKnownPath)
}
return result
}

View file

@ -432,6 +432,11 @@ func (c *Commit) GetBranchName() (string, error) {
return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil
}
// GetAllBranches returns a slice with all branches that contains this commit
func (c *Commit) GetAllBranches() ([]string, error) {
return c.repo.getBranches(c, -1)
}
// CommitFileStatus represents status of files in a commit.
type CommitFileStatus struct {
Added []string

View file

@ -5,6 +5,7 @@ package git
import (
"path/filepath"
"slices"
"strings"
"testing"
@ -370,6 +371,23 @@ func TestParseCommitRenames(t *testing.T) {
}
}
func TestGetAllBranches(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
require.NoError(t, err)
commit, err := bareRepo1.GetCommit("95bb4d39648ee7e325106df01a621c530863a653")
require.NoError(t, err)
branches, err := commit.GetAllBranches()
require.NoError(t, err)
slices.Sort(branches)
assert.Equal(t, []string{"branch1", "branch2", "master"}, branches)
}
func Test_parseSubmoduleContent(t *testing.T) {
submoduleFiles := []struct {
fileContent string

View file

@ -278,6 +278,49 @@ func syncGitConfig() (err error) {
return err
}
switch setting.Repository.Signing.Format {
case "ssh":
// First do a git version check.
if CheckGitVersionAtLeast("2.34.0") != nil {
return errors.New("ssh signing requires Git >= 2.34.0")
}
// Get the ssh-keygen binary that Git will use.
// This can be overriden in app.ini in [git.config] section, so we must
// query this information.
sshKeygenPath, err := configGet("gpg.ssh.program")
if err != nil {
return err
}
// git is very stubborn and does not give a default value, so we must do
// this ourselves.
if len(sshKeygenPath) == 0 {
// Default value of git, very unlikely to change.
// https://github.com/git/git/blob/5b97a56fa0e7d580dc8865b73107407c9b3f0eff/gpg-interface.c#L116
sshKeygenPath = "ssh-keygen"
}
// Although there's a version requirement of 8.2p1, there's no cross-version
// method to get the version of ssh-keygen. Therefore we do a simple binary
// presence check and hope for the best.
if _, err := exec.LookPath(sshKeygenPath); err != nil {
if errors.Is(err, exec.ErrNotFound) {
return errors.New("git signing requires a ssh-keygen binary")
}
return err
}
if err := configSet("gpg.format", "ssh"); err != nil {
return err
}
// openpgp is already the default value, so in the case of a non SSH format
// set the value to openpgp.
default:
if err := configSet("gpg.format", "openpgp"); err != nil {
return err
}
}
// By default partial clones are disabled, enable them from git v2.22
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
@ -324,6 +367,15 @@ func CheckGitVersionEqual(equal string) error {
return nil
}
func configGet(key string) (string, error) {
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
if err != nil && !IsErrorExitCode(err, 1) {
return "", fmt.Errorf("failed to get git config %s, err: %w", key, err)
}
return strings.TrimSpace(stdout), nil
}
func configSet(key, value string) error {
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
if err != nil && !IsErrorExitCode(err, 1) {

View file

@ -11,8 +11,10 @@ import (
"testing"
"forgejo.org/modules/setting"
"forgejo.org/modules/test"
"forgejo.org/modules/util"
"github.com/hashicorp/go-version"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -94,3 +96,57 @@ func TestSyncConfig(t *testing.T) {
assert.True(t, gitConfigContains("[sync-test]"))
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
}
func TestSyncConfigGPGFormat(t *testing.T) {
defer test.MockProtect(&setting.GitConfig)()
t.Run("No format", func(t *testing.T) {
defer test.MockVariableValue(&setting.Repository.Signing.Format, "")()
require.NoError(t, syncGitConfig())
assert.True(t, gitConfigContains("[gpg]"))
assert.True(t, gitConfigContains("format = openpgp"))
})
t.Run("SSH format", func(t *testing.T) {
r, err := os.OpenRoot(t.TempDir())
require.NoError(t, err)
f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700)
require.NoError(t, f.Close())
require.NoError(t, err)
t.Setenv("PATH", r.Name())
defer test.MockVariableValue(&setting.Repository.Signing.Format, "ssh")()
require.NoError(t, syncGitConfig())
assert.True(t, gitConfigContains("[gpg]"))
assert.True(t, gitConfigContains("format = ssh"))
t.Run("Old version", func(t *testing.T) {
oldVersion, err := version.NewVersion("2.33.0")
require.NoError(t, err)
defer test.MockVariableValue(&gitVersion, oldVersion)()
require.ErrorContains(t, syncGitConfig(), "ssh signing requires Git >= 2.34.0")
})
t.Run("No ssh-keygen binary", func(t *testing.T) {
require.NoError(t, r.Remove("ssh-keygen"))
require.ErrorContains(t, syncGitConfig(), "git signing requires a ssh-keygen binary")
})
t.Run("Dynamic ssh-keygen binary location", func(t *testing.T) {
f, err := r.OpenFile("ssh-keygen-2", os.O_CREATE|os.O_TRUNC, 0o700)
require.NoError(t, f.Close())
require.NoError(t, err)
defer test.MockVariableValue(&setting.GitConfig.Options, map[string]string{
"gpg.ssh.program": "ssh-keygen-2",
})()
require.NoError(t, syncGitConfig())
})
})
t.Run("OpenPGP format", func(t *testing.T) {
defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")()
require.NoError(t, syncGitConfig())
assert.True(t, gitConfigContains("[gpg]"))
assert.True(t, gitConfigContains("format = openpgp"))
})
}

View file

@ -444,10 +444,13 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
if CheckGitVersionAtLeast("2.7.0") == nil {
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").
AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commit.ID.String(), BranchPrefix).
RunStdString(&RunOpts{Dir: repo.Path})
command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix)
if limit != -1 {
command = command.AddOptionFormat("--count=%d", limit)
}
stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}

View file

@ -260,11 +260,11 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
if opts.Mode == internal.CodeSearchModeUnion {
query := bleve.NewDisjunctionQuery()
for _, field := range strings.Fields(opts.Keyword) {
query.AddQuery(inner_bleve.MatchPhraseQuery(field, "Content", repoIndexerAnalyzer, 0))
query.AddQuery(inner_bleve.MatchPhraseQuery(field, "Content", repoIndexerAnalyzer, false))
}
keywordQuery = query
} else {
keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, 0)
keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, false)
}
if len(opts.RepoIDs) > 0 {

View file

@ -65,5 +65,7 @@ func TokenizerConstructor(config map[string]any, cache *registry.Cache) (analysi
}
func init() {
registry.RegisterTokenizer(Name, TokenizerConstructor)
if err := registry.RegisterTokenizer(Name, TokenizerConstructor); err != nil {
panic(err)
}
}

View file

@ -29,11 +29,11 @@ func MatchQuery(matchTerm, field, analyzer string, fuzziness int) *query.MatchQu
}
// MatchPhraseQuery generates a match phrase query for the given phrase, field and analyzer
func MatchPhraseQuery(matchPhrase, field, analyzer string, fuzziness int) *query.MatchPhraseQuery {
func MatchPhraseQuery(matchPhrase, field, analyzer string, autoFuzzy bool) *query.MatchPhraseQuery {
q := bleve.NewMatchPhraseQuery(matchPhrase)
q.FieldVal = field
q.Analyzer = analyzer
q.Fuzziness = fuzziness
q.SetAutoFuzziness(autoFuzzy)
return q
}

View file

@ -162,15 +162,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
}
q := bleve.NewBooleanQuery()
for _, token := range tokens {
fuzziness := 0
if token.Fuzzy {
// TODO: replace with "auto" after bleve update
fuzziness = min(len(token.Term)/4, 2)
}
innerQ := bleve.NewDisjunctionQuery(
inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, fuzziness),
inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, fuzziness),
inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, fuzziness))
inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, token.Fuzzy),
inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, token.Fuzzy),
inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, token.Fuzzy))
switch token.Kind {
case internal.BoolOptMust:

View file

@ -22,6 +22,7 @@ import (
type MockRedisClient struct {
ctrl *gomock.Controller
recorder *MockRedisClientMockRecorder
isgomock struct{}
}
// MockRedisClientMockRecorder is the mock recorder for MockRedisClient.
@ -56,38 +57,38 @@ func (mr *MockRedisClientMockRecorder) Close() *gomock.Call {
}
// DBSize mocks base method.
func (m *MockRedisClient) DBSize(arg0 context.Context) *redis.IntCmd {
func (m *MockRedisClient) DBSize(ctx context.Context) *redis.IntCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DBSize", arg0)
ret := m.ctrl.Call(m, "DBSize", ctx)
ret0, _ := ret[0].(*redis.IntCmd)
return ret0
}
// DBSize indicates an expected call of DBSize.
func (mr *MockRedisClientMockRecorder) DBSize(arg0 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) DBSize(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBSize", reflect.TypeOf((*MockRedisClient)(nil).DBSize), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBSize", reflect.TypeOf((*MockRedisClient)(nil).DBSize), ctx)
}
// Decr mocks base method.
func (m *MockRedisClient) Decr(arg0 context.Context, arg1 string) *redis.IntCmd {
func (m *MockRedisClient) Decr(ctx context.Context, key string) *redis.IntCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Decr", arg0, arg1)
ret := m.ctrl.Call(m, "Decr", ctx, key)
ret0, _ := ret[0].(*redis.IntCmd)
return ret0
}
// Decr indicates an expected call of Decr.
func (mr *MockRedisClientMockRecorder) Decr(arg0, arg1 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) Decr(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decr", reflect.TypeOf((*MockRedisClient)(nil).Decr), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decr", reflect.TypeOf((*MockRedisClient)(nil).Decr), ctx, key)
}
// Del mocks base method.
func (m *MockRedisClient) Del(arg0 context.Context, arg1 ...string) *redis.IntCmd {
func (m *MockRedisClient) Del(ctx context.Context, keys ...string) *redis.IntCmd {
m.ctrl.T.Helper()
varargs := []any{arg0}
for _, a := range arg1 {
varargs := []any{ctx}
for _, a := range keys {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Del", varargs...)
@ -96,17 +97,17 @@ func (m *MockRedisClient) Del(arg0 context.Context, arg1 ...string) *redis.IntCm
}
// Del indicates an expected call of Del.
func (mr *MockRedisClientMockRecorder) Del(arg0 any, arg1 ...any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) Del(ctx any, keys ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...)
varargs := append([]any{ctx}, keys...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockRedisClient)(nil).Del), varargs...)
}
// Exists mocks base method.
func (m *MockRedisClient) Exists(arg0 context.Context, arg1 ...string) *redis.IntCmd {
func (m *MockRedisClient) Exists(ctx context.Context, keys ...string) *redis.IntCmd {
m.ctrl.T.Helper()
varargs := []any{arg0}
for _, a := range arg1 {
varargs := []any{ctx}
for _, a := range keys {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Exists", varargs...)
@ -115,45 +116,45 @@ func (m *MockRedisClient) Exists(arg0 context.Context, arg1 ...string) *redis.In
}
// Exists indicates an expected call of Exists.
func (mr *MockRedisClientMockRecorder) Exists(arg0 any, arg1 ...any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) Exists(ctx any, keys ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...)
varargs := append([]any{ctx}, keys...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockRedisClient)(nil).Exists), varargs...)
}
// FlushDB mocks base method.
func (m *MockRedisClient) FlushDB(arg0 context.Context) *redis.StatusCmd {
func (m *MockRedisClient) FlushDB(ctx context.Context) *redis.StatusCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FlushDB", arg0)
ret := m.ctrl.Call(m, "FlushDB", ctx)
ret0, _ := ret[0].(*redis.StatusCmd)
return ret0
}
// FlushDB indicates an expected call of FlushDB.
func (mr *MockRedisClientMockRecorder) FlushDB(arg0 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) FlushDB(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushDB", reflect.TypeOf((*MockRedisClient)(nil).FlushDB), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushDB", reflect.TypeOf((*MockRedisClient)(nil).FlushDB), ctx)
}
// Get mocks base method.
func (m *MockRedisClient) Get(arg0 context.Context, arg1 string) *redis.StringCmd {
func (m *MockRedisClient) Get(ctx context.Context, key string) *redis.StringCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0, arg1)
ret := m.ctrl.Call(m, "Get", ctx, key)
ret0, _ := ret[0].(*redis.StringCmd)
return ret0
}
// Get indicates an expected call of Get.
func (mr *MockRedisClientMockRecorder) Get(arg0, arg1 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) Get(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisClient)(nil).Get), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisClient)(nil).Get), ctx, key)
}
// HDel mocks base method.
func (m *MockRedisClient) HDel(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntCmd {
func (m *MockRedisClient) HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs := []any{ctx, key}
for _, a := range fields {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HDel", varargs...)
@ -162,31 +163,31 @@ func (m *MockRedisClient) HDel(arg0 context.Context, arg1 string, arg2 ...string
}
// HDel indicates an expected call of HDel.
func (mr *MockRedisClientMockRecorder) HDel(arg0, arg1 any, arg2 ...any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) HDel(ctx, key any, fields ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
varargs := append([]any{ctx, key}, fields...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HDel", reflect.TypeOf((*MockRedisClient)(nil).HDel), varargs...)
}
// HKeys mocks base method.
func (m *MockRedisClient) HKeys(arg0 context.Context, arg1 string) *redis.StringSliceCmd {
func (m *MockRedisClient) HKeys(ctx context.Context, key string) *redis.StringSliceCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HKeys", arg0, arg1)
ret := m.ctrl.Call(m, "HKeys", ctx, key)
ret0, _ := ret[0].(*redis.StringSliceCmd)
return ret0
}
// HKeys indicates an expected call of HKeys.
func (mr *MockRedisClientMockRecorder) HKeys(arg0, arg1 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) HKeys(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HKeys", reflect.TypeOf((*MockRedisClient)(nil).HKeys), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HKeys", reflect.TypeOf((*MockRedisClient)(nil).HKeys), ctx, key)
}
// HSet mocks base method.
func (m *MockRedisClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
func (m *MockRedisClient) HSet(ctx context.Context, key string, values ...any) *redis.IntCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs := []any{ctx, key}
for _, a := range values {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HSet", varargs...)
@ -195,73 +196,73 @@ func (m *MockRedisClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) *
}
// HSet indicates an expected call of HSet.
func (mr *MockRedisClientMockRecorder) HSet(arg0, arg1 any, arg2 ...any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) HSet(ctx, key any, values ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
varargs := append([]any{ctx, key}, values...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSet", reflect.TypeOf((*MockRedisClient)(nil).HSet), varargs...)
}
// Incr mocks base method.
func (m *MockRedisClient) Incr(arg0 context.Context, arg1 string) *redis.IntCmd {
func (m *MockRedisClient) Incr(ctx context.Context, key string) *redis.IntCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Incr", arg0, arg1)
ret := m.ctrl.Call(m, "Incr", ctx, key)
ret0, _ := ret[0].(*redis.IntCmd)
return ret0
}
// Incr indicates an expected call of Incr.
func (mr *MockRedisClientMockRecorder) Incr(arg0, arg1 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) Incr(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisClient)(nil).Incr), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisClient)(nil).Incr), ctx, key)
}
// LLen mocks base method.
func (m *MockRedisClient) LLen(arg0 context.Context, arg1 string) *redis.IntCmd {
func (m *MockRedisClient) LLen(ctx context.Context, key string) *redis.IntCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LLen", arg0, arg1)
ret := m.ctrl.Call(m, "LLen", ctx, key)
ret0, _ := ret[0].(*redis.IntCmd)
return ret0
}
// LLen indicates an expected call of LLen.
func (mr *MockRedisClientMockRecorder) LLen(arg0, arg1 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) LLen(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLen", reflect.TypeOf((*MockRedisClient)(nil).LLen), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLen", reflect.TypeOf((*MockRedisClient)(nil).LLen), ctx, key)
}
// LPop mocks base method.
func (m *MockRedisClient) LPop(arg0 context.Context, arg1 string) *redis.StringCmd {
func (m *MockRedisClient) LPop(ctx context.Context, key string) *redis.StringCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LPop", arg0, arg1)
ret := m.ctrl.Call(m, "LPop", ctx, key)
ret0, _ := ret[0].(*redis.StringCmd)
return ret0
}
// LPop indicates an expected call of LPop.
func (mr *MockRedisClientMockRecorder) LPop(arg0, arg1 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) LPop(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LPop", reflect.TypeOf((*MockRedisClient)(nil).LPop), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LPop", reflect.TypeOf((*MockRedisClient)(nil).LPop), ctx, key)
}
// Ping mocks base method.
func (m *MockRedisClient) Ping(arg0 context.Context) *redis.StatusCmd {
func (m *MockRedisClient) Ping(ctx context.Context) *redis.StatusCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Ping", arg0)
ret := m.ctrl.Call(m, "Ping", ctx)
ret0, _ := ret[0].(*redis.StatusCmd)
return ret0
}
// Ping indicates an expected call of Ping.
func (mr *MockRedisClientMockRecorder) Ping(arg0 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) Ping(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockRedisClient)(nil).Ping), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockRedisClient)(nil).Ping), ctx)
}
// RPush mocks base method.
func (m *MockRedisClient) RPush(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
func (m *MockRedisClient) RPush(ctx context.Context, key string, values ...any) *redis.IntCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs := []any{ctx, key}
for _, a := range values {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "RPush", varargs...)
@ -270,17 +271,17 @@ func (m *MockRedisClient) RPush(arg0 context.Context, arg1 string, arg2 ...any)
}
// RPush indicates an expected call of RPush.
func (mr *MockRedisClientMockRecorder) RPush(arg0, arg1 any, arg2 ...any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) RPush(ctx, key any, values ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
varargs := append([]any{ctx, key}, values...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RPush", reflect.TypeOf((*MockRedisClient)(nil).RPush), varargs...)
}
// SAdd mocks base method.
func (m *MockRedisClient) SAdd(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
func (m *MockRedisClient) SAdd(ctx context.Context, key string, members ...any) *redis.IntCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs := []any{ctx, key}
for _, a := range members {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "SAdd", varargs...)
@ -289,31 +290,31 @@ func (m *MockRedisClient) SAdd(arg0 context.Context, arg1 string, arg2 ...any) *
}
// SAdd indicates an expected call of SAdd.
func (mr *MockRedisClientMockRecorder) SAdd(arg0, arg1 any, arg2 ...any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) SAdd(ctx, key any, members ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
varargs := append([]any{ctx, key}, members...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SAdd", reflect.TypeOf((*MockRedisClient)(nil).SAdd), varargs...)
}
// SIsMember mocks base method.
func (m *MockRedisClient) SIsMember(arg0 context.Context, arg1 string, arg2 any) *redis.BoolCmd {
func (m *MockRedisClient) SIsMember(ctx context.Context, key string, member any) *redis.BoolCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SIsMember", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "SIsMember", ctx, key, member)
ret0, _ := ret[0].(*redis.BoolCmd)
return ret0
}
// SIsMember indicates an expected call of SIsMember.
func (mr *MockRedisClientMockRecorder) SIsMember(arg0, arg1, arg2 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) SIsMember(ctx, key, member any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SIsMember", reflect.TypeOf((*MockRedisClient)(nil).SIsMember), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SIsMember", reflect.TypeOf((*MockRedisClient)(nil).SIsMember), ctx, key, member)
}
// SRem mocks base method.
func (m *MockRedisClient) SRem(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
func (m *MockRedisClient) SRem(ctx context.Context, key string, members ...any) *redis.IntCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs := []any{ctx, key}
for _, a := range members {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "SRem", varargs...)
@ -322,22 +323,22 @@ func (m *MockRedisClient) SRem(arg0 context.Context, arg1 string, arg2 ...any) *
}
// SRem indicates an expected call of SRem.
func (mr *MockRedisClientMockRecorder) SRem(arg0, arg1 any, arg2 ...any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) SRem(ctx, key any, members ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
varargs := append([]any{ctx, key}, members...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SRem", reflect.TypeOf((*MockRedisClient)(nil).SRem), varargs...)
}
// Set mocks base method.
func (m *MockRedisClient) Set(arg0 context.Context, arg1 string, arg2 any, arg3 time.Duration) *redis.StatusCmd {
func (m *MockRedisClient) Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Set", arg0, arg1, arg2, arg3)
ret := m.ctrl.Call(m, "Set", ctx, key, value, expiration)
ret0, _ := ret[0].(*redis.StatusCmd)
return ret0
}
// Set indicates an expected call of Set.
func (mr *MockRedisClientMockRecorder) Set(arg0, arg1, arg2, arg3 any) *gomock.Call {
func (mr *MockRedisClientMockRecorder) Set(ctx, key, value, expiration any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisClient)(nil).Set), arg0, arg1, arg2, arg3)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisClient)(nil).Set), ctx, key, value, expiration)
}

View file

@ -9,7 +9,7 @@ var defaultI18nLangNames = []string{
"zh-CN", "简体中文",
"zh-HK", "繁體中文(香港)",
"zh-TW", "繁體中文(台灣)",
"da", "Danish",
"da", "Dansk",
"de-DE", "Deutsch",
"nds", "Plattdüütsch",
"fr-FR", "Français",

View file

@ -62,7 +62,7 @@ type MarkupSanitizerRule struct {
func loadMarkupFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "markdown", &Markdown)
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(50000)
FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)

View file

@ -4,12 +4,15 @@
package setting
import (
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"forgejo.org/modules/log"
"golang.org/x/crypto/ssh"
)
// enumerates all the policy repository creating
@ -26,6 +29,8 @@ var MaxUserCardsPerPage = 36
// MaxForksPerPage sets maximum amount of forks shown per page
var MaxForksPerPage = 40
var SSHInstanceKey ssh.PublicKey
// Repository settings
var (
Repository = struct {
@ -109,6 +114,7 @@ var (
SigningKey string
SigningName string
SigningEmail string
Format string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
@ -262,6 +268,7 @@ var (
SigningKey string
SigningName string
SigningEmail string
Format string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
@ -271,6 +278,7 @@ var (
SigningKey: "default",
SigningName: "",
SigningEmail: "",
Format: "openpgp",
InitialCommit: []string{"always"},
CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
@ -376,4 +384,15 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
log.Fatal("loadRepoArchiveFrom: %v", err)
}
Repository.EnableFlags = sec.Key("ENABLE_FLAGS").MustBool()
if Repository.Signing.Format == "ssh" && Repository.Signing.SigningKey != "none" && Repository.Signing.SigningKey != "" {
sshPublicKey, err := os.ReadFile(Repository.Signing.SigningKey)
if err != nil {
log.Fatal("Could not read repository signing key in %q: %v", Repository.Signing.SigningKey, err)
}
SSHInstanceKey, _, _, _, err = ssh.ParseAuthorizedKey(sshPublicKey)
if err != nil {
log.Fatal("Could not parse the SSH signing key %q: %v", sshPublicKey, err)
}
}
}

View file

@ -0,0 +1,59 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
import (
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)
func TestSSHInstanceKey(t *testing.T) {
sshSigningKeyPath, err := filepath.Abs("../../tests/integration/ssh-signing-key.pub")
require.NoError(t, err)
t.Run("None value", func(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[repository.signing]
FORMAT = ssh
SIGNING_KEY = none
`)
require.NoError(t, err)
loadRepositoryFrom(cfg)
assert.Nil(t, SSHInstanceKey)
})
t.Run("No value", func(t *testing.T) {
cfg, err := NewConfigProviderFromData(`
[repository.signing]
FORMAT = ssh
`)
require.NoError(t, err)
loadRepositoryFrom(cfg)
assert.Nil(t, SSHInstanceKey)
})
t.Run("Normal", func(t *testing.T) {
iniStr := fmt.Sprintf(`
[repository.signing]
FORMAT = ssh
SIGNING_KEY = %s
`, sshSigningKeyPath)
cfg, err := NewConfigProviderFromData(iniStr)
require.NoError(t, err)
loadRepositoryFrom(cfg)
assert.NotNil(t, SSHInstanceKey)
assert.Equal(t, "ssh-ed25519", SSHInstanceKey.Type())
assert.EqualValues(t, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeRC8GfFyXtiy0f1E7hLv77BXW7e68tFvIcs8/29YqH\n", ssh.MarshalAuthorizedKey(SSHInstanceKey))
})
}

View file

@ -10,3 +10,11 @@ type CreateForkOption struct {
// name of the forked repository
Name *string `json:"name"`
}
// SyncForkInfo information about syncing a fork
type SyncForkInfo struct {
Allowed bool `json:"allowed"`
ForkCommit string `json:"fork_commit"`
BaseCommit string `json:"base_commit"`
CommitsBehind int `json:"commits_behind"`
}

View file

@ -4,6 +4,8 @@
package structs
import "time"
// FileOptions options for all file APIs
type FileOptions struct {
// message (optional) for the commit of this file. if not supplied, a default message will be used
@ -121,6 +123,8 @@ type ContentsResponse struct {
Path string `json:"path"`
SHA string `json:"sha"`
LastCommitSHA string `json:"last_commit_sha"`
// swagger:strfmt date-time
LastCommitWhen time.Time `json:"last_commit_when"`
// `type` will be `file`, `dir`, `symlink`, or `submodule`
Type string `json:"type"`
Size int64 `json:"size"`

View file

@ -1,4 +1,5 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package i18n
@ -205,6 +206,7 @@ func (l *locale) TrString(trKey string, trArgs ...any) string {
if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok {
if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" {
format = msg
found = true
} else if foundIndex {
// Third fallback: old-style default language
if msg, ok := defaultLang.idxToMsgMap[idx]; ok {

View file

@ -1,5 +1,4 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2023 The Forgejo Authors. All rights reserved.
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package validation
@ -33,9 +32,9 @@ type Validateable interface {
}
func IsValid(v Validateable) (bool, error) {
if err := v.Validate(); len(err) > 0 {
if valdationErrors := v.Validate(); len(valdationErrors) > 0 {
typeof := reflect.TypeOf(v)
errString := strings.Join(err, "\n")
errString := strings.Join(valdationErrors, "\n")
return false, ErrNotValid{fmt.Sprint(typeof, ": ", errString)}
}
@ -53,6 +52,10 @@ func ValidateNotEmpty(value any, name string) []string {
if v.IsZero() {
isValid = false
}
case uint16:
if v == 0 {
isValid = false
}
case int64:
if v == 0 {
isValid = false

View file

@ -1414,7 +1414,7 @@ editor.file_is_a_symlink=`„%s“ je symbolický odkaz. Symbolické odkazy nemo
editor.filename_is_a_directory=Jméno souboru „%s“ je již použito jako jméno adresáře v tomto repozitáři.
editor.file_editing_no_longer_exists=Upravovaný soubor „%s“ již není součástí tohoto repozitáře.
editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není součástí tohoto repozitáře.
editor.file_changed_while_editing=Obsah souboru se od zahájení úprav změnil. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte sem</a> pro jeho zobrazení nebo <strong>odešlete změny ještě jednou</strong> pro jeho přepsání.
editor.file_changed_while_editing=Obsah souboru se od doby, kdy jste ho otevřeli, změnil. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte sem</a> pro jeho zobrazení nebo <strong>odešlete změny ještě jednou</strong> pro jeho přepsání.
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
editor.commit_empty_file_header=Odeslat prázdný soubor
editor.commit_empty_file_text=Soubor, který se chystáte odeslat, je prázdný. Pokračovat?
@ -2912,6 +2912,9 @@ issues.filter_no_results = Žádné výsledky
migrate.repo_desc_helper = Ponechte prázdné pro importování existujícího popisu
archive.nocomment = Komentování není možné, protože je repozitář archivován.
comment.blocked_by_user = Komentování není možné, protože jste byli zablokováni majitelem repozitáře nebo autorem.
sync_fork.branch_behind_few = Tato větev je %d revizí pozadu za %s
sync_fork.button = Synchronizovat
sync_fork.branch_behind_one = Tato větev je %d revizi pozadu za %s
[graphs]
component_loading_info = Tohle může chvíli trvat…
@ -3930,7 +3933,7 @@ runners.delete_runner=Odstranit tento runner
runners.delete_runner_success=Runner byl úspěšně odstraněn
runners.delete_runner_failed=Odstranění runneru selhalo
runners.delete_runner_header=Potvrdit odstranění tohoto runneru
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření pracovního postupu.
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření workflow.
runners.status.unspecified=Neznámý
runners.status.idle=Nečinný
runners.status.active=Aktivní

View file

@ -2725,6 +2725,9 @@ branch.rename_branch_to = Omdøb "%s" til:
migrate.repo_desc_helper = Lad være tom for at importere eksisterende beskrivelse
archive.nocomment = Det er ikke muligt at kommentere, fordi depotet er arkiveret.
comment.blocked_by_user = Det er ikke muligt at kommentere, fordi du er blokeret af depots ejer eller forfatteren.
sync_fork.branch_behind_few = Denne gren er %d commits bag %s
sync_fork.button = Sync
sync_fork.branch_behind_one = Denne gren er %d commit bag %s
[notification]
watching = Overvåger

View file

@ -1412,7 +1412,7 @@ editor.file_is_a_symlink=`„%s“ ist ein symbolischer Link. Symbolische Links
editor.filename_is_a_directory=Der Dateiname „%s“ wird bereits als Verzeichnisname in diesem Repository verwendet.
editor.file_editing_no_longer_exists=Die bearbeitete Datei „%s“ existiert nicht mehr in diesem Repository.
editor.file_deleting_no_longer_exists=Die zu löschende Datei „%s“ existiert nicht mehr in diesem Repository.
editor.file_changed_while_editing=Der Inhalt der Datei hat sich seit dem Beginn der Bearbeitung geändert. <a target="_blank" rel="noopener noreferrer" href="%s">Hier klicken</a>, um die Änderungen anzusehen, oder <strong>Änderungen erneut comitten</strong>, um sie zu überschreiben.
editor.file_changed_while_editing=Der Inhalt der Datei hat sich nach dem Öffnen geändert. <a target="_blank" rel="noopener noreferrer" href="%s">Hier klicken</a>, um die Änderungen anzusehen, oder <strong>Änderungen erneut committen</strong>, um sie zu überschreiben.
editor.file_already_exists=Eine Datei mit dem Namen „%s“ existiert bereits in diesem Repository.
editor.commit_empty_file_header=Leere Datei committen
editor.commit_empty_file_text=Die Datei, die du commiten willst, ist leer. Fortfahren?
@ -2914,6 +2914,9 @@ issues.filter_no_results_placeholder = Versuche, deine Suchfilter anzupassen.
migrate.repo_desc_helper = Leer lassen, um vorhandene Beschreibung zu importieren
archive.nocomment = Kommentieren ist nicht möglich, da das Repository archiviert ist.
comment.blocked_by_user = Kommentieren ist nicht möglich, da du vom Repository-Besitzer oder vom Autor blockiert wurdest.
sync_fork.branch_behind_one = Dieser Branch ist %d Commit hinter %s
sync_fork.branch_behind_few = Dieser Branch ist %d Commits hinter %s
sync_fork.button = Sync
[graphs]
component_loading_failed = Konnte %s nicht laden

View file

@ -1220,6 +1220,10 @@ archive.title_date = This repository has been archived on %s. You can view files
archive.nocomment = Commenting is not possible because the repository is archived.
archive.pull.noreview = This repository is archived. You cannot review pull requests.
sync_fork.branch_behind_one = This branch is %[1]d commit behind %[2]s
sync_fork.branch_behind_few = This branch is %[1]d commits behind %[2]s
sync_fork.button = Sync
form.reach_limit_of_creation_1 = The owner has already reached the limit of %d repository.
form.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories.
form.name_reserved = The repository name "%s" is reserved.
@ -3814,7 +3818,7 @@ owner.settings.cleanuprules.success.update = Cleanup rule has been updated.
owner.settings.cleanuprules.success.delete = Cleanup rule has been deleted.
owner.settings.chef.title = Chef registry
owner.settings.chef.keypair = Generate key pair
owner.settings.chef.keypair.description = A key pair is necessary to authenticate to the Chef registry. If you have generated a key pair before, generating a new key pair will discard the old key pair.
owner.settings.chef.keypair.description = Requests sent to the Chef registry must be cryptographically signed as a means of authentication. When generating a keypair, only the public key is stored on Forgejo. The private key is provided to you to be used with knife. Generating a new keypair will overwrite the previous one.
[secrets]
secrets = Secrets
@ -3871,7 +3875,7 @@ runners.delete_runner = Delete this runner
runners.delete_runner_success = Runner deleted successfully
runners.delete_runner_failed = Failed to delete runner
runners.delete_runner_header = Confirm to delete this runner
runners.delete_runner_notice = If a task is running on this runner, it will be terminated and mark as failed. It may break building workflow.
runners.delete_runner_notice = If a task is running on this runner, it will be terminated and marked as failed. It may break building workflow.
runners.none = No runners available
runners.status.unspecified = Unknown
runners.status.idle = Idle

View file

@ -38,12 +38,12 @@ passcode=Código de acceso
webauthn_insert_key=Introduzca su clave de seguridad
webauthn_sign_in=Presione el botón en su clave de seguridad. Si su clave de seguridad no tiene ningún botón, vuelva a insertarla.
webauthn_press_button=Por favor, presione el botón de su llave de seguridad…
webauthn_press_button=Por favor, presione el botón en su clave de seguridad…
webauthn_use_twofa=Utilice un código de doble factor desde su teléfono móvil
webauthn_error=No se pudo leer su llave de seguridad.
webauthn_unsupported_browser=Su navegador no soporta actualmente WebAuthn.
webauthn_error_unknown=Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo.
webauthn_error_insecure=`WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1"`
webauthn_error_insecure=WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1"
webauthn_error_unable_to_process=El servidor no pudo procesar su solicitud.
webauthn_error_duplicated=La clave de seguridad no está permitida para esta solicitud. Por favor, asegúrese de que la clave no está ya registrada.
webauthn_error_empty=Debe establecer un nombre para esta clave.
@ -72,7 +72,7 @@ all=Todos
sources=Propios
mirrors=Réplica
collaborative=Colaborativo
forks=Forks
forks=Bifurcaciones
activities=Actividades
pull_requests=Solicitudes de incorporación de cambios
@ -300,7 +300,7 @@ offline_mode.description=Deshabilitar redes de distribución de contenido de ter
disable_gravatar=Desactivar Gravatar
disable_gravatar.description=Desactivar el Gravatar y otros fuentes de avatares de terceros. Se utilizará un avatar por defecto a menos que un usuario suba un avatar localmente.
federated_avatar_lookup=Habilitar avatares federados
federated_avatar_lookup.description=Buscar de avatares con Libravatar.
federated_avatar_lookup.description=Busca avatares con Libravatar.
disable_registration=Deshabilitar auto-registro
disable_registration.description=Sólo los administradores de la instancia podrán crear nuevas cuentas. Es muy recomendable mantener deshabilitado el registro a menos que pretenda alojar una instancia pública para todo el mundo y esté preparado para lidiar con grandes cantidades de cuentas de spam.
allow_only_external_registration.description=Los usuarios sólo podrán crear nuevas cuentas utilizando servicios externos configurados.
@ -668,7 +668,7 @@ still_own_packages=Tu cuenta posee uno o más paquetes, elimínalos primero.
org_still_own_repo=Esta organización todavía posee uno o más repositorios, elimínalos o transfiérelos primero.
org_still_own_packages=Esta organización todavía posee uno o más paquetes, elimínalos primero.
target_branch_not_exist=La rama de destino no existe
target_branch_not_exist=La rama de destino no existe.
admin_cannot_delete_self = No puedes eliminarte a ti mismo cuando eres un admin (administrador). Por favor, elimina primero tus privilegios de administrador.
username_error_no_dots = ` solo puede contener carácteres alfanuméricos ("0-9","a-z","A-Z"), guiones ("-"), y guiones bajos ("_"). No puede empezar o terminar con carácteres no alfanuméricos y también están prohibidos los carácteres no alfanuméricos consecutivos.`
unsupported_login_type = No se admite el tipo de inicio de sesión para eliminar la cuenta.
@ -843,7 +843,7 @@ add_email_success=La nueva dirección de correo electrónico ha sido añadida.
email_preference_set_success=La preferencia de correo electrónico se ha establecido correctamente.
add_openid_success=La nueva dirección OpenID ha sido añadida.
keep_email_private=Ocultar dirección de correo electrónico
keep_email_private_popup=Esto ocultará tu dirección de correo electrónico de tu perfil. Ya no será la dirección predeterminada para los confirmaciones realizadas a través de la interfaz web, como las subidas y ediciones de archivos, y no se utilizará para las confirmaciones de fusión. En su lugar, se utilizará una dirección especial %s para asociar las confirmaciones a tu cuenta. Ten en cuenta que cambiar esta opción no afectará a las confirmaciones existentes.
keep_email_private_popup=Su dirección de correo electrónico no se mostrará en su perfil y no será la predeterminada para las confirmaciones realizadas a través de la interfaz web, como las subidas de archivos, las ediciones y las confirmaciones de fusión. En su lugar, se utilizará una dirección especial %s para vincular las confirmaciones a tu cuenta. Esta opción no afectará a las confirmaciones existentes.
openid_desc=OpenID le permite delegar la autenticación a un proveedor externo.
manage_ssh_keys=Gestionar claves SSH
@ -1075,6 +1075,14 @@ keep_pronouns_private = Mostrar pronombres solo a personas autenticadas
storage_overview = Resumen del almacenamiento
quota.sizes.assets.artifacts = Artefactos
quota.sizes.assets.attachments.releases = Archivos adjuntos del lanzamiento
change_username_redirect_prompt.with_cooldown.few = El antiguo nombre de usuario estará disponible para todos después un periodo de tiempo de espera de %[1]d días, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
change_username_redirect_prompt.with_cooldown.one = El antiguo nombre de usuario estará disponible para todos después un periodo de tiempo de espera de %[1]d día, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
quota.rule.exceeded = Excedido
quota.rule.no_limit = Ilimitado
quota.sizes.assets.all = Activos
quota.sizes.git.lfs = Git LFS
quota.sizes.assets.attachments.issues = Archivos adjuntos de incidencia
access_token_regeneration = Regenerar token de acceso
[repo]
owner=Propietario
@ -1237,7 +1245,7 @@ migrate.migrate_items_options=Se necesita un token de acceso para migrar element
migrated_from=Migrado desde <a href="%[1]s">%[2]s</a>
migrated_from_fake=Migrado desde %[1]s
migrate.migrate=Migrar desde %s
migrate.migrating=Migrando desde <b>%s</b>...
migrate.migrating=Migrando desde <b>%s</b>
migrate.migrating_failed=La migración desde <b>%s</b> ha fallado.
migrate.migrating_failed.error=Error al migrar: %s
migrate.migrating_failed_no_addr=Migración fallida.
@ -1510,7 +1518,7 @@ issues.new.no_projects=Ningún proyecto
issues.new.open_projects=Proyectos abiertos
issues.new.closed_projects=Proyectos cerrados
issues.new.no_items=No hay elementos
issues.new.milestone=Milestone
issues.new.milestone=Hito
issues.new.no_milestone=Sin hito
issues.new.clear_milestone=Limpiar Milestone
issues.new.open_milestone=Hitos abiertos
@ -1558,12 +1566,12 @@ issues.change_title_at=`cambió el título de <b><strike>%s</strike></b> a <b>%s
issues.change_ref_at=`cambió referencia de <b><strike>%s</strike></b> a <b>%s</b> %s`
issues.remove_ref_at=`eliminó la referencia <b>%s</b> %s`
issues.add_ref_at=`añadió la referencia <b>%s</b> %s`
issues.delete_branch_at=`rama eliminada <b>%s</b> %s`
issues.delete_branch_at=`eliminó la rama <b>%s</b> %s`
issues.filter_label=Etiqueta
issues.filter_label_exclude=`Usa <code>alt</code> + <code>clic/enter</code> para excluir etiquetas`
issues.filter_label_no_select=Todas las etiquetas
issues.filter_label_select_no_label=Sin etiqueta
issues.filter_milestone=Milestone
issues.filter_milestone=Hito
issues.filter_milestone_all=Todos los hitos
issues.filter_milestone_none=Sin hitos
issues.filter_milestone_open=Abrir hitos
@ -1965,7 +1973,7 @@ pulls.auto_merge_canceled_schedule_comment=`canceló la fusión automática de e
pulls.delete.title=¿Borrar este pull request?
pulls.delete.text=¿Realmente quieres eliminar esta pull request? (Esto eliminará permanentemente todo el contenido. Considera cerrarlo si simplemente deseas archivarlo)
pulls.recently_pushed_new_branches=Has realizado push en la rama <strong>%[1]s</strong> %[2]s
pulls.recently_pushed_new_branches=Empujaste en la rama <a href="%[3]s"><strong>%[1]s</strong></a> %[2]s
pull.deleted_branch=(eliminado):%s
@ -1976,7 +1984,7 @@ milestones.no_due_date=Sin fecha límite
milestones.open=Abrir
milestones.close=Cerrar
milestones.new_subheader=Los hitos pueden ayudarle a organizar los problemas y monitorizar su progreso.
milestones.completeness=%d%% Completado
milestones.completeness=<strong>%d%%</strong> Completado
milestones.create=Crear hito
milestones.title=Título
milestones.desc=Descripción
@ -2017,7 +2025,7 @@ ext_wiki=Wiki externa
ext_wiki.desc=Enlace a una wiki externa.
wiki=Wiki
wiki.welcome=¡Bienvenidos a la Wiki!
wiki.welcome=Bienvenido a la Wiki.
wiki.welcome_desc=Esta wiki le permite escribir y compartir documentación con otros colaboradores.
wiki.desc=Escriba y comparta documentación con colaboradores.
wiki.create_first_page=Crear la primera página
@ -2326,7 +2334,7 @@ settings.event_create=Crear
settings.event_create_desc=Rama o etiqueta creada.
settings.event_delete=Eliminar
settings.event_delete_desc=Rama o etiqueta eliminada.
settings.event_fork=Fork
settings.event_fork=Bifurcación
settings.event_fork_desc=Repositorio forkeado.
settings.event_wiki=Wiki
settings.event_wiki_desc=Página de la Wiki creada, renombrada, editada o eliminada.
@ -2514,7 +2522,7 @@ settings.archive.branchsettings_unavailable=Los ajustes de rama no están dispon
settings.archive.tagsettings_unavailable=Los ajustes de las etiquetas no están disponibles si el repositorio está archivado.
settings.unarchive.button=Desarchivar repositorio
settings.unarchive.header=Desarchivar este repositorio
settings.unarchive.text=La desarchivación del repositorio restablecerá su capacidad de recibir confirmaciones y subidos, así como nuevas incidencias y solicitudes de incorporación de cambios.
settings.unarchive.text=La desarchivación del repositorio restablecerá su capacidad de recibir confirmaciones y empujes, así como nuevas incidencias y solicitudes de incorporación de cambios.
settings.unarchive.success=El repositorio se ha desarchivado correctamente.
settings.unarchive.error=Ocurrió un error mientras se trataba de des-archivar el repositorio. Revisa el registro para más detalles.
settings.update_avatar_success=El avatar del repositorio ha sido actualizado.
@ -2532,7 +2540,7 @@ settings.lfs_invalid_locking_path=Ruta no válida: %s
settings.lfs_invalid_lock_directory=No se puede bloquear el directorio: %s
settings.lfs_lock_already_exists=El bloqueo ya existe: %s
settings.lfs_lock=Bloquear
settings.lfs_lock_path=Ruta del archivo a bloquear...
settings.lfs_lock_path=Ruta del archivo a bloquear
settings.lfs_locks_no_locks=Sin bloqueos
settings.lfs_lock_file_no_exist=El archivo bloqueado no existe en la rama por defecto
settings.lfs_force_unlock=Forzar desbloqueo
@ -2637,7 +2645,7 @@ release.cancel=Cancelar
release.publish=Publicar lanzamiento
release.save_draft=Guardar borrador
release.edit_release=Actualizar Lanzamiento
release.delete_release=Eliminar Lanzamiento
release.delete_release=Eliminar lanzamiento
release.delete_tag=Eliminar tag
release.deletion=Eliminar lanzamiento
release.deletion_desc=Eliminar un lanzamiento sólo lo elimina de Forgejo. No afectará la etiqueta Git, el contenido de su repositorio o su historial. ¿Continuar?
@ -2862,9 +2870,15 @@ release.add_external_asset = Añadir un recurso externo
settings.enforce_on_admins_desc = Los administradores del repositorio no pueden saltarse esta regla.
pulls.editable = Editable
issues.filter_no_results = No hay resultados
release.type_attachment = Archivo adjunto
sync_fork.button = Sincronizar
settings.sourcehut_builds.visibility = Visibilidad de trabajo
settings.ignore_stale_approvals = Ignorar las aprobaciones obsoletas
settings.event_pull_request_enforcement = Aplicación
issues.reaction.alt_few = %[1]s reaccionado con %[2]s.
[graphs]
component_loading = Cargando %s...
component_loading = Cargando %s
component_loading_failed = No se pudo cargar %s
contributors.what = contribuciones
recent_commits.what = commits recientes
@ -2929,11 +2943,11 @@ settings.hooks_desc=Añadir webhooks que serán ejecutados para <strong>todos lo
settings.labels_desc=Añadir etiquetas que pueden ser utilizadas en problemas para <strong>todos los repositorios</strong> bajo esta organización.
members.membership_visibility=Visibilidad de Membresía:
members.membership_visibility=Visibilidad de membresía:
members.public=Público
members.public_helper=hacer oculto
members.public_helper=Hacer oculto
members.private=Oculto
members.private_helper=hacer público
members.private_helper=Hacer público
members.member_role=Rol del miembro:
members.owner=Propietario
members.member=Miembro
@ -2942,7 +2956,7 @@ members.remove.detail=¿Destituir a %[1]s de %[2]s?
members.leave=Abandonar
members.leave.detail=¿Irse de %s?
members.invite_desc=Añadir un miembro nuevo a %s:
members.invite_now=Invitar
members.invite_now=Invitar ahora
teams.join=Unirse
teams.leave=Abandonar
@ -2951,7 +2965,7 @@ teams.can_create_org_repo=Crear repositorios
teams.can_create_org_repo_helper=Los miembros pueden crear nuevos repositorios en la organización. El creador obtendrá acceso al administrador del nuevo repositorio.
teams.none_access=Sin acceso
teams.none_access_helper=Los miembros no pueden ver o hacer ninguna otra acción en esta unidad.
teams.general_access=Acceso general
teams.general_access=Acceso personalizado
teams.general_access_helper=Los permisos de los miembros se decidirán por debajo de la tabla de permisos.
teams.read_access=Leer
teams.read_access_helper=Los miembros pueden ver y clonar los repositorios del equipo.
@ -2997,7 +3011,7 @@ teams.invite.by=Invitado por %s
teams.invite.description=Por favor, haga clic en el botón de abajo para unirse al equipo.
follow_blocked_user = No puedes seguir a esta organización porque esta organización te ha bloqueado.
open_dashboard = Abrir panel de control
settings.change_orgname_redirect_prompt.with_cooldown.few = El antiguo nombre de usuario estará disponible para cualquiera después un periodo de tiempo de espera de %[1]d días, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
settings.change_orgname_redirect_prompt.with_cooldown.few = El antiguo nombre de la organización estará disponible para todos después un periodo de tiempo de espera de %[1]d días, aún puedes reclamar el antiguo nombre durante el periodo de tiempo de espera.
settings.change_orgname_redirect_prompt.with_cooldown.one = El antiguo nombre de usuario estará disponible para cualquiera después un periodo de tiempo de espera de %[1]d día, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
[admin]
@ -3539,6 +3553,9 @@ emails.delete_primary_email_error = No puedes eliminar el correo electrónico pr
config.cache_test =Caché de prueba
emails.delete_desc = ¿Estás seguro que quieres eliminar esta dirección de correo electrónico?
monitor.duration = Duración (es)
self_check = Autocomprobación
config.app_slogan = Eslogan de la instancia
dashboard.sync_tag.started = Sincronización de etiquetas iniciada
[action]
@ -3799,6 +3816,12 @@ alt.install = Instalar paquete
alt.repository = Información del repositorio
alt.repository.architectures = Arquitecturas
alt.repository.multiple_groups = Este paquete está disponible en múltiples grupos.
arch.version.description = Descripción
arch.version.provides = Proveedores
npm.dependencies.bundle = Empaquetar dependencias
arch.version.checkdepends = Comprobar dependencias
arch.version.optdepends = Dependencias opcionales
arch.version.makedepends = Construir dependencias
[secrets]
secrets=Secretos
@ -3904,6 +3927,8 @@ variables.id_not_exist = Variable con id %d no existe.
runs.empty_commit_message = (mensaje de commit vacío)
runs.expire_log_message = Los registros han sido eliminados porque eran demasiado antiguos.
runs.workflow = Flujo de trabajo
workflow.dispatch.run = Correr flujo de trabajo
workflow.dispatch.use_from = Usar el flujo de trabajo de
[projects]
type-1.display_name=Proyecto individual
@ -3946,7 +3971,7 @@ exact = Exacto
exact_tooltip = Incluir sólo los resultados que corresponden al término de búsqueda exacto
issue_kind = Buscar incidencias…
fuzzy = Difusa
runner_kind = Buscar ejecutores…
runner_kind = Buscar corredores…
regexp_tooltip = Interpretar los términos de búsqueda como una expresión regular
regexp = Expresión Regular

View file

@ -0,0 +1 @@

View file

@ -545,6 +545,7 @@ issue_assigned.issue = @%[1]s osoitti sinulle ongelman %[2]s repossa %[3]s.
register_notify.text_1 = tämä on %s:n rekistöröitymisen vahvistussähköposti!
reset_password.text = jos tämä oli sinun toimestasi, ole hyvä ja klikkaa oheista linkkiä palauttaaksesi tilisi <b>%s</b> sisällä:
totp_disabled.no_2fa = Muita kaksivaiheisen tunnistautumisen menetelmiä ei ole konfiguroituna, joten et tarvitse kaksivaiheista tunnistautumista kirjautuaaksesi tilillesi.
totp_enrolled.subject = Olet aktivoinut TOTP:in kaksivaiheisen todennuksen menetelmäksi
@ -666,6 +667,8 @@ follow_blocked_user = Et voi seurata tätä käyttäjää, koska olet estänyt k
disabled_public_activity = Käyttäjä on poistanut käytöstä toiminnan julkisen näkyvyyden.
form.name_reserved = Käyttäjätunnus "%s" on varattu.
form.name_pattern_not_allowed = Kaava "%s" ei ole sallittu käyttäjätunnuksessa.
public_activity.visibility_hint.admin_private = Aktiivisuus on näkyvissä sinulle, koska olet ylläpitäjä, mutta käyttäjä haluaa pitää aktiivisuutensa yksityisenä.
public_activity.visibility_hint.self_private_profile = Aktiivisuutesi on näkyvissä vain sinulle ja instanssin ylläpitäjille, koska profiilisi on yksityinen. <a href="%s">Määritä</a>.
[settings]
@ -994,12 +997,12 @@ download_tar=Lataa TAR.GZ
repo_desc=Kuvaus
repo_lang=Kieli
repo_gitignore_helper=Valitse .gitignore-mallit
issue_labels=Ongelmien tunnisteet
issue_labels=Tunnisteet
issue_labels_helper=Valitse nimiöjoukko
license=Lisenssi
license_helper=Valitse lisenssitiedosto
readme=README
auto_init=Alusta repo (Luo .gitignore, License ja README)
auto_init=Alusta repo
create_repo=Luo repo
default_branch=Oletushaara
mirror_prune=Karsi
@ -1168,7 +1171,7 @@ issues.new_label=Uusi tunniste
issues.new_label_placeholder=Tunnisteen nimi
issues.new_label_desc_placeholder=Kuvaus
issues.create_label=Luo tunniste
issues.label_templates.helper=Valitse tunnistejoukko
issues.label_templates.helper=Valitse tunnisteen esiasetus
issues.add_milestone_at=`lisäsi tämän merkkipaaluun <b>%s</b> %s`
issues.change_milestone_at=`vaihtoi merkkipaalun <b>%s</b> merkkipaaluun <b>%s</b> %s`
issues.remove_milestone_at=`poisti tämän <b>%s</b> merkkipaalusta %s`
@ -1244,7 +1247,7 @@ issues.label_count=%d tunnistetta
issues.label_open_issues=%d avointa ongelmaa
issues.label_edit=Muokkaa
issues.label_delete=Poista
issues.label_modify=Muokkaa tunniste
issues.label_modify=Muokkaa tunnistetta
issues.label_deletion=Poista tunniste
issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä
issues.label.filter_sort.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
@ -1272,7 +1275,7 @@ issues.start_tracking_history=`aloitti työskentelyn %s`
issues.tracker_auto_close=Ajan seuranta pysähtyy automaattisesti kun tämä ongelma on suljettu
issues.stop_tracking=Pysäytä ajanotto
issues.stop_tracking_history=`lopetti työskentelyn %s`
issues.add_time=Lisää aika käsin
issues.add_time=Lisää aika manuaalisesti
issues.add_time_short=Lisää aika
issues.add_time_cancel=Peruuta
issues.add_time_history=`lisäsi käytetyn ajan %s`
@ -2129,13 +2132,62 @@ archive.pull.noreview = Tämä repo on arkistoitu. Et voi katselmoida vetopyynt
tree_path_not_found_tag = Polkua %[1]s ei ole olemassa tagissa %[2]s
transfer.no_permission_to_accept = Sinulla ei ole oikeutta hyväksyä tätä siirtoa.
settings.web_hook_name_feishu = Feishu / Lark Suite
issues.review.reviewers = Katselmoijat
issues.new.no_reviewers = Ei katselmoijia
issues.add_label = lisäsi tunnisteen %s %s
issues.due_date_added = lisäsi määräpäivän %s %s
issues.review.add_review_request = pyysi katselmointia käyttäjältä %[1]s %[2]s
issues.ref_pull_from = `<a href="%[3]s">viittasi tähän vetopyyntöön %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.commit_ref_at = `viittasi tähän vetopyyntöön kommitista <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.review.comment = katselmoi %s
issues.add_labels = lisäsi tunnisteet %s %s
issues.review.add_review_requests = pyysi katselmointeja käyttäjiltä %[1]s %[2]s
pulls.blocked_by_official_review_requests = Tämä vetopyyntö on estetty, koska siltä puuttuu hyväksyntä yhdeltä tai useammalta viralliselta katselmoijalta.
issues.author.tooltip.issue = Tämä käyttäjä on tämän ongelman tekijä.
issues.author.tooltip.pr = Tämä käyttäjä on tämän vetopyynnön tekijä.
issues.role.contributor_helper = Tämä käyttäjä on aiemmin kommitoinut tähän repoon.
settings.event_pull_request_label = Tunnisteet
issues.due_date_remove = poisti määräpäivän %s %s
settings.event_issue_label = Tunnisteet
settings.authorization_header = Authorization-otsake
diff.has_escaped = Tällä rivillä on piilotettuja Unicode-merkkejä
issues.max_pinned = Et voi kiinnittää enempää ongelmia
settings.external_tracker_url_error = Ulkoisen ongelmienseurannan URL-osoite ei ole kelvollinen.
settings.event_pull_request_review = Katselmoinnit
settings.event_pull_request_review_request = Katselmointipyynnöt
issues.num_reviews_one = %d katselmointi
issues.num_reviews_few = %d katselmointia
issues.filter_no_results = Ei tuloksia
issues.filter_no_results_placeholder = Kokeile määrittää eri hakusuodattimet.
projects.edit_subheader = Projektit organisoivat ongelmia ja seuraavat edistymistä.
issues.label_templates.title = Lataa tunnisteen esiasetus
issues.label_deletion_desc = Tunnisteen poistaminen poistaa sen kaikista ongelmista. Jatketaanko?
issues.attachment.download = `Napsauta ladataksesi "%s"`
issues.review.option.hide_outdated_comments = Piilota vanhentuneet kommentit
issues.review.show_outdated = Näytä vanhentuneet
issues.review.hide_outdated = Piilota vanhentuneet
settings.external_wiki_url_error = Ulkoisen wikin URL-osoite ei ole kelvollinen.
issues.attachment.open_tab = `Napsauta nähdäksesi "%s" uudessa välilehdessä`
issues.unpin_issue = Poista ongelman kiinnitys
issues.review.outdated_description = Sisältö on muuttunut siitä ajanhetkestä, kun tämä kommentti luotiin
issues.review.option.show_outdated_comments = Näytä vanhentuneet kommentit
issues.review.outdated = Vanhentunut
issues.label_templates.use = Käytä tunnisteen esiasetusta
issues.label_deletion_success = Tunniste on poistettu.
issues.cancel_tracking = Hylkää
issues.choose.get_started = Aloitetaan
settings.event_fork = Forkkaus
pulls.no_merge_access = Sinulla ei ole valtuutta yhdistää tätä vetopyyntöä.
pulls.sign_in_require = <a href="%s">Kirjaudu sisään</a> luodaksesi uuden vetopyynnön.
delete_preexisting = Poista olemassa olevat tiedostot
issues.reaction.add = Lisää reaktio
[graphs]
component_loading_info = Tämä saattaa kestää hetken…
component_failed_to_load = Odottamaton virhe.
component_loading = Ladataan %s...
component_loading = Ladataan %s
contributors.what = kontribuutiot
recent_commits.what = viimeisimmät kommitit
code_frequency.what = koodifrekvenssi
@ -2319,7 +2371,7 @@ users.password_helper=Jätä salasanakenttä tyhjäksi jos haluat pitää sen mu
users.update_profile_success=Käyttäjän tili on päivitetty.
users.edit_account=Muokkaa käyttäjätiliä
users.max_repo_creation_desc=(Aseta -1 käyttääksesi globaalia oletusrajaa.)
users.is_activated=Käyttäjätili on aktivoitu
users.is_activated=Aktivoitu tili
users.prohibit_login=Ota sisäänkirjautuminen pois käytöstä
users.is_admin=Ylläpitäjätili
users.is_restricted=Rajoitettu tili
@ -2406,7 +2458,7 @@ auths.edit=Muokkaa todennuslähdettä
auths.update_success=Todennuslähde on päivitetty.
auths.update=Päivitä todennuslähde
auths.delete=Poista todennuslähde
auths.delete_auth_title=Todennuslähteen poisto
auths.delete_auth_title=Poista todennuslähde
auths.delete_auth_desc=Todennuslähteen poisto estää käyttäjiä käyttämästä sitä kirjautumiseen. Jatketaanko?
auths.deletion_success=Todennuslähde on poistettu.
@ -2445,7 +2497,7 @@ config.show_registration_button=Näytä rekisteröitymispainike
config.enable_captcha=Ota CAPTCHA käyttöön
config.active_code_lives=Aktivointikoodin vanhenemisaika
config.default_keep_email_private=Piilota sähköpostiosoitteet oletuksena
config.default_visibility_organization=Uuden organisaation oletusnäkyvyys
config.default_visibility_organization=Uusien organisaatioiden oletusnäkyvyys
config.webhook_config=Webkoukkujen asetukset
config.queue_length=Jonon pituus
@ -2468,7 +2520,7 @@ config.cache_conn=Välimuistin yhteys merkkijono
config.session_config=Istunnon asetukset
config.session_provider=Istunnon toimittaja
config.provider_config=Toimittajan asetukset
config.cookie_name=Evästenimi
config.cookie_name=Evästeen nimi
config.gc_interval_time=GC aikaväli aika
config.session_life_time=Istunnon elinikä
config.https_only=Vain HTTPS
@ -2602,6 +2654,12 @@ config.default_allow_only_contributors_to_track_time = Salli vain avustajien seu
monitor.download_diagnosis_report = Lataa diagnostiikkaraportti
monitor.duration = Kesto (s)
monitor.last_execution_result = Tulos
users.bot = Botti
auths.syncenabled = Käytä käyttäjäsynkronointia
auths.enable_ldap_groups = Käytä LDAP-ryhmiä
dashboard.sync_branch.started = Haarasynkronointi aloitettu
dashboard.sync_tag.started = Tagisynkronointi aloitettu
auths.login_source_exist = Todennuslähde "%s" on jo olemassa.
[action]

View file

@ -1536,7 +1536,7 @@ pulls.merged_by =ni/ng <a href="%[2]s">%[3]s</a> ay naisama %[1]s
commitstatus.pending = Nakabinbin
issues.review.pending = Nakabinbin
pulls.status_checking = Nakabinbin ang ilang mga pagsusuri
editor.file_changed_while_editing = Ang nilalaman ng file ay nagbago mula noong nagsimula kang mag-edit. <a target="_blank" rel="noopener noreferrer" href="%s">Mag-click dito</a> upang makita ang mga pagbabago o <strong>Mag-commit ng mga pagbabago muli</strong> para i-overwrite sila.
editor.file_changed_while_editing = Ang nilalaman ng file ay nagbago mula noong binuksan mo ang file. <a target="_blank" rel="noopener noreferrer" href="%s">Mag-click dito</a> upang makita ang mga pagbabago o <strong>Mag-commit ng mga pagbabago muli</strong> para i-overwrite sila.
editor.file_already_exists = Umiiral na ang file na may pangalang "%s" sa repositoryong ito.
issues.review.review = Suriin
activity.git_stats_push_to_branch = sa %s at
@ -2770,6 +2770,9 @@ issues.filter_no_results_placeholder = Subukang ayusin ang iyong mga filter sa p
migrate.repo_desc_helper = Iwanang walang laman para i-import ang umiiral na paglalarawan
archive.nocomment = Hindi posible ang pagkomento dahil naka-archive ang repositoryo.
comment.blocked_by_user = Hindi posible ang pagkomento dahil hinarang ka ng may-ari ng repositoryo o ng may-akda.
sync_fork.button = I-sync
sync_fork.branch_behind_one = Ang branch na ito ay %d commit sa likod ng %s
sync_fork.branch_behind_few = Ang branch na ito ay %d mga commit sa likod ng %s
[search]
commit_kind = Maghanap ng mga commit…

View file

@ -1064,6 +1064,32 @@ user_block_yourself = Vous ne pouvez pas vous bloquer vous même.
pronouns_custom_label = Pronoms personnalisés
change_username_redirect_prompt.with_cooldown.one = L'ancien pseudonyme sera disponible pour n'importe qui après une période d'%[1]d jour, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
change_username_redirect_prompt.with_cooldown.few = L'ancien pseudonyme sera disponible pour n'importe qui après une période de %[1]d jours, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
quota.rule.exceeded = Dépassé
regenerate_token = Régénérer
access_token_regeneration = Régénérer le token d'accès
access_token_regeneration_desc = La régénération d'un token révoquera l'accès à votre compte pour les applications qui l'utilisaient. Cela n'est pas reversible. Continuer ?
regenerate_token_success = Le token a été régénéré. Les applications qui l'utilisent n'ont plus accès à votre compte et doivent être mises à jour avec le nouveau token.
quota.applies_to_org = Les quotas suivants s'applique à cette organisation
quota.rule.no_limit = Sans limite
quota.sizes.all = Tout
quota.sizes.repos.all = Dépôts
quota.sizes.repos.public = Dépôts publics
quota.sizes.repos.private = Dépôts privés
quota.sizes.git.all = Contenu dans Git
quota.sizes.git.lfs = Git LFS
quota.sizes.assets.all = Contenus
quota.sizes.assets.attachments.all = Attachements
quota.sizes.assets.attachments.issues = Attachements de tickets
quota.sizes.assets.attachments.releases = Attachements de version
quota.sizes.assets.artifacts = Artefacts
quota.sizes.assets.packages.all = Paquets
quota.sizes.wiki = Wiki
quota.applies_to_user = Les quotas suivants s'appliquent à votre compte
quota.rule.exceeded.helper = La taille totale des objets pour cette règle ont dépassé le quota.
keep_pronouns_private = Ne montrer les pronoms qu'aux utilisateurs authentifiés
keep_pronouns_private.description = Cela masquera votre pronoms aux visiteurs qui ne sont pas authentifiés.
storage_overview = Vue d'ensemble du stockage
quota = Quota
[repo]
new_repo_helper=Un dépôt contient tous les fichiers dun projet, ainsi que lhistorique de leurs modifications. Vous avez déjà ça ailleurs ? <a href="%s">Migrez-le ici.</a>.
@ -1228,7 +1254,7 @@ migrate.migrate_items_options=Un jeton d'accès est requis pour migrer des élé
migrated_from=Migré de <a href="%[1]s">%[2]s</a>
migrated_from_fake=Migré de %[1]s
migrate.migrate=Migrer depuis %s
migrate.migrating=Migration de <b>%s</b> ...
migrate.migrating=Migration de <b>%s</b>
migrate.migrating_failed=La migration de <b>%s</b> a échoué.
migrate.migrating_failed.error=Échec de la migration : %s
migrate.migrating_failed_no_addr=Échec de la migration.
@ -1388,7 +1414,7 @@ editor.file_is_a_symlink=`« %s » est un lien symbolique. Ce type de fichiers
editor.filename_is_a_directory=« %s » est déjà utilisé comme nom de dossier dans ce dépôt.
editor.file_editing_no_longer_exists=Impossible de modifier le fichier « %s » car il nexiste plus dans ce dépôt.
editor.file_deleting_no_longer_exists=Impossible de supprimer le fichier « %s » car il nexiste plus dans ce dépôt.
editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez commencé à éditer. <a target="_blank" rel="noopener noreferrer" href="%s">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.
editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez ouvert le fichier. <a target="_blank" rel="noopener noreferrer" href="%s">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.
editor.file_already_exists=Un fichier nommé "%s" existe déjà dans ce dépôt.
editor.commit_empty_file_header=Réviser un fichier vide
editor.commit_empty_file_text=Le fichier que vous allez réviser est vide. Continuer ?
@ -2540,7 +2566,7 @@ settings.lfs_invalid_locking_path=Chemin invalide : %s
settings.lfs_invalid_lock_directory=Impossible de verrouiller le répertoire : %s
settings.lfs_lock_already_exists=Verrou déjà existant : %s
settings.lfs_lock=Verrou
settings.lfs_lock_path=Chemin de fichier à verrouiller...
settings.lfs_lock_path=Chemin de fichier à verrouiller
settings.lfs_locks_no_locks=Pas de verrous
settings.lfs_lock_file_no_exist=Le fichier verrouillé n'existe pas dans la branche par défaut
settings.lfs_force_unlock=Forcer le déverrouillage
@ -2732,7 +2758,7 @@ editor.invalid_commit_mail = Courriel invalide pour la création d'un commit.
commits.browse_further = Continuer la navigation
commits.renamed_from = Renommé depuis %s
pulls.nothing_to_compare_have_tag = La branche ou le tag sélectionné sont identiques.
issues.blocked_by_user = Vous ne pouvez pas créer un ticket sur ce dépôt car vous avez été bloqué par son propriétaire.
issues.blocked_by_user = Vous ne pouvez pas créer de tickets sur ce dépôt car vous avez été bloqué par son propriétaire.
pulls.blocked_by_user = Vous ne pouvez pas créer une pull request sur ce dépôt car vous êtes bloqué par son propriétaire.
wiki.cancel = Annuler
settings.wiki_globally_editable = Permettre l'édition du wiki a tout le monde
@ -2876,9 +2902,20 @@ summary_card_alt = Carte résumé du dépôt %s
archive.pull.noreview = Ce dépôt est archivé. Vous ne pouvez pas faire de revue de demandes d'ajout.
editor.commit_email = Courriel de commit
commits.view_single_diff = Voir les changements dans ce fichier introduit par ce commit
issues.reopen.blocked_by_user = Vous ne pouvez pas ré-ouvrir ce ticket care vous êtes bloqués par le propriétaire du dépôt ou le créateur de ce ticket.
migrate.repo_desc_helper = Laisser vide afin d'importer une description existante
issues.filter_no_results = Pas de résultats
issues.filter_no_results_placeholder = Essayez d'ajuster vos critères de recherche.
archive.nocomment = Il n'est pas possible de commenter car le dépôt est archivé.
comment.blocked_by_user = Il n'est pas possible de commenter car vous avez été bloqué par le propriétaire du dépôt ou l'auteur.
pulls.editable = Editable
pulls.editable_explanation = Cette pull request peut être éditée par les mainteneurs. Vous pouvez y contribuer directement.
sync_fork.branch_behind_one = Cette branche a %d commits de retard sur %s
sync_fork.branch_behind_few = Cettte branche a %d commits de retard sur %s
sync_fork.button = Sync
[graphs]
component_loading = Chargement %s...
component_loading = Chargement %s
component_loading_failed = Échec de chargement de %s
component_loading_info = Cela peut prendre du temps…
@ -3013,8 +3050,8 @@ teams.invite.by=Invité par %s
teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindre léquipe.
follow_blocked_user = Vous ne pouvez pas suivre cette organisation car elle vous a bloqué.
open_dashboard = Ouvrir le tableau de bord
settings.change_orgname_redirect_prompt.with_cooldown.few = L'ancien pseudonyme sera disponible pour n'importe qui après une période de %[1]d jours, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
settings.change_orgname_redirect_prompt.with_cooldown.one = L'ancien pseudonyme sera disponible pour n'importe qui après une période d'%[1]d jour, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
settings.change_orgname_redirect_prompt.with_cooldown.few = L'ancien nom d'organisation sera disponible pour n'importe qui après une période de %[1]d jours, vous pouvez toujours réclamer votre ancien nom d'organisation pendant cette période.
settings.change_orgname_redirect_prompt.with_cooldown.one = L'ancien nom d'organisation sera disponible pour n'importe qui après une période d'%[1]d jour, vous pouvez toujours réclamer votre ancien nom d'organisation pendant cette période.
[admin]
dashboard=Tableau de bord
@ -3840,7 +3877,7 @@ alt.registry = Configurez ce registre à partir d'un terminal :
alt.registry.install = Pour installer le paquet, exécutez la commande suivante :
alt.install = Installer le paquet
alt.repository.multiple_groups = Ce paquet est disponible dans plusieurs groupes.
alt.setup = Ajouter un dépôt à la liste des dépôts connecté (choisissez l'architecture nécessaire à la place de '_arch') :
alt.setup = Ajouter un dépôt à la liste des dépôts connecté (choisissez l'architecture nécessaire à la place de "_arch") :
[secrets]
secrets=Secrets
@ -4005,7 +4042,7 @@ exact_tooltip = Inclure uniquement les résultats qui correspondent exactement a
issue_kind = Rechercher dans les tickets…
union = Union
union_tooltip = Inclus les résultats contenant au moins un des mots clé séparés par des espaces
pull_kind = Rechercher dans les demande d'ajout
pull_kind = Rechercher dans les demande d'ajout
milestone_kind = Recherche dans les jalons...
regexp_tooltip = Interpréter le terme de recherche comme une expression régulière
regexp = RegExp
@ -4046,4 +4083,4 @@ issues.write = <b>Écrire :</b> Fermer des tickets et gérer les métadonnées t
pulls.read = <b>Lire :</b> Lire et créer des demandes de tirage.
[translation_meta]
test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce doux 100 % de complétion :)
test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce doux 100 % de complétion. :-)

View file

@ -291,3 +291,8 @@ app_slogan = Slogan da instancia
app_slogan_helper = Escribe o slogan da túa instancia aqui. Ou deixao baleiro para desabilitala.
domain = Dominio do servidor
ssh_port = Porto do servidor SSH
[repo]
sync_fork.branch_behind_few = Esta rama ten %d achegas por detrás de %s
sync_fork.button = Sincronizar
sync_fork.branch_behind_one = Esta rama ten %d achega por detrás de %s

View file

@ -228,7 +228,7 @@ server_internal = Iekšēja servera kļūda
app_desc=Pašmitināms Git pakalpojums bez galvassāpēm
install=Viegli uzstādīt
install_desc=Vienkārši <a target="_blank" rel="noopener noreferrer" href="%[1]s">jāpalaiž izpildāmā datne</a> vajadzīgajai sistēmai, jāizmanto <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a> vai jāiegūst <a target="_blank" rel="noopener noreferrer" href="%[3]s">pakotne</a>.
platform=Pieejama dažādām platformām
platform=Dažādas platformas
lightweight=Viegla
lightweight_desc=Forgejo ir zemas tehniskās prasības, un to var darbināt nedārgā Raspberry Pi datorā. Taupām savas ierīces patērēto enerģiju!
license=Atvērtā pirmkoda
@ -1412,7 +1412,7 @@ editor.file_is_a_symlink=`"%s" ir simboliska saite. Simboliskās saites tīmekļ
editor.filename_is_a_directory=Datnes nosaukums "%s" šajā glabātavā jau tiek izmantos kā mapes nosaukums.
editor.file_editing_no_longer_exists=Datne, kas tiek labota ("%s"), šajā glabātavā vairs nepastāv.
editor.file_deleting_no_longer_exists=Datne, kas tiek izdzēsta ("%s"), šajā glabātavā vairs nepastāv.
editor.file_changed_while_editing=Datnes saturs ir mainījies kopš labošanas uzsākšanas. <a target="_blank" rel="noopener noreferrer" href="%s">Klikšķināt šeit</a>, lai apskatītu vai <strong>atkārtoti iesūtītu izmaiņas</strong>, lai tās pārrakstītu.
editor.file_changed_while_editing=Datnes saturs ir mainījies kopš tās atvēršanas. <a target="_blank" rel="noopener noreferrer" href="%s">Klikšķināt šeit</a>, lai apskatītu vai <strong>atkārtoti iesūtītu izmaiņas</strong>, lai tās pārrakstītu.
editor.file_already_exists=Datne ar nosaukumu "%s" jau pastāv šajā glabātavā.
editor.commit_empty_file_header=Iesūtīt tukšu datni
editor.commit_empty_file_text=Iesūtāmā datne ir tukša. Turpināt?
@ -2910,6 +2910,9 @@ issues.filter_no_results_placeholder = Jāmēģina pielāgot meklēšanas atlas
migrate.repo_desc_helper = Atstāt tukšu, lai ievietotu esošo aprakstu
archive.nocomment = Piebilžu pievienošana nav iespējama, jo glabātava ir arhivēta.
comment.blocked_by_user = Piebilžu pievienošana nav iespējama, jo glabātavas īpašnieks vai autors ir nolieguši Tevi.
sync_fork.branch_behind_one = Šis zars ir %d iesūtījumu aiz %s
sync_fork.button = Sinhronizēt
sync_fork.branch_behind_few = Šis zars ir %d iesūtījumus aiz %s
[graphs]
component_loading=Ielādē %s…

View file

@ -2611,6 +2611,9 @@ issues.filter_no_results_placeholder = Versöök, diene Söök-Filters antopasse
migrate.repo_desc_helper = Leeg laten, um de bestahn Beschrieven to importeren
comment.blocked_by_user = Kommenteren gaht hier nich, denn du büst vun de Repositoriums-Eegner of de Autor blockeert worden.
archive.nocomment = Kommenteren gaht hier nich, denn dat Repositorium is archiveert.
sync_fork.button = Vernejen
sync_fork.branch_behind_one = Deeser Twieg is %d Kommitteren achter %s
sync_fork.branch_behind_few = Deeser Twieg is %d Kommitterens achter %s
[repo.permissions]
code.read = <b>Lesen:</b> De Quelltext vun deesem Repositorium ankieken un klonen.

View file

@ -2909,6 +2909,9 @@ issues.filter_no_results = Geen resultaten
migrate.repo_desc_helper = Leeg laten om bestaande beschrijving te importeren
archive.nocomment = Commentaar geven is niet mogelijk omdat de repository gearchiveerd is.
comment.blocked_by_user = Commentaar geven is niet mogelijk omdat u geblokkeerd bent door de eigenaar van de repository of door de auteur.
sync_fork.button = Synchroniseer
sync_fork.branch_behind_one = Deze branch is %d commit achter %s
sync_fork.branch_behind_few = Deze branch is %d commits achter %s

View file

@ -1405,7 +1405,7 @@ editor.file_is_a_symlink=`"%s" é um link simbólico. Links simbólicos não pod
editor.filename_is_a_directory=O nome do arquivo "%s" já é usado como um nome de diretório neste repositório.
editor.file_editing_no_longer_exists=O arquivo que está sendo editado, "%s", não existe mais neste repositório.
editor.file_deleting_no_longer_exists=O arquivo a ser excluído, "%s", não existe mais neste repositório.
editor.file_changed_while_editing=O conteúdo do arquivo mudou desde que você começou a editar. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as diferenças ou <strong>clique em Aplicar commit das alterações novamente</strong> para sobrescrever as alterações com sua versão atual.
editor.file_changed_while_editing=O conteúdo do arquivo mudou desde que você abriu o arquivo. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as diferenças ou <strong>clique em Aplicar commit das alterações novamente</strong> para sobrescrever as alterações com sua versão atual.
editor.file_already_exists=Um arquivo com nome "%s" já existe neste repositório.
editor.commit_empty_file_header=Fazer commit de um arquivo vazio
editor.commit_empty_file_text=O arquivo que você está prestes fazer commit está vazio. Continuar?
@ -2910,6 +2910,9 @@ issues.filter_no_results_placeholder = Tente ajustar seus filtros de pesquisa.
archive.nocomment = Não é possível comentar pois o repositório foi arquivado.
migrate.repo_desc_helper = Deixe em branco para importar a descrição existente
comment.blocked_by_user = Não é possível comentar pois você foi bloqueado pelo dono ou autor do repositório.
sync_fork.branch_behind_few = Esta branch está %d commit(s) atrás de %s
sync_fork.branch_behind_one = Esta branch está %d commit(s) atrás de %s
sync_fork.button = Sincronizar
[graphs]
component_loading = Carregando %s…

View file

@ -1418,7 +1418,7 @@ editor.file_is_a_symlink=`"%s" é uma ligação simbólica. Ligações simbólic
editor.filename_is_a_directory=O nome de ficheiro "%s" já está a ser usado como um nome de pasta neste repositório.
editor.file_editing_no_longer_exists=O ficheiro que está a ser editado, "%s", já não existe neste repositório.
editor.file_deleting_no_longer_exists=O ficheiro que está a ser eliminado, "%s", já não existe neste repositório.
editor.file_changed_while_editing=O conteúdo do ficheiro mudou desde que começou a editar. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as modificações ou clique em <strong>Cometer modificações novamente</strong> para escrever por cima.
editor.file_changed_while_editing=O conteúdo do ficheiro mudou desde que abriu o ficheiro. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as modificações ou <strong>Cometer modificações novamente</strong> para escrever por cima.
editor.file_already_exists=Já existe um ficheiro com o nome "%s" neste repositório.
editor.commit_empty_file_header=Cometer um ficheiro vazio
editor.commit_empty_file_text=O ficheiro que está prestes a cometer está vazio. Quer continuar?
@ -2912,6 +2912,9 @@ issues.filter_no_results_placeholder = Tente ajustar os seus filtros de pesquisa
migrate.repo_desc_helper = Deixe em branco para importar a descrição existente
archive.nocomment = Não é possível fazer comentários porque o repositório está arquivado.
comment.blocked_by_user = Não é possível comentar porque está bloqueado pelo proprietário do repositório ou pelo autor.
sync_fork.branch_behind_few = Este ramo está %d cometimentos atrás de %s
sync_fork.button = Sincronizar
sync_fork.branch_behind_one = Este ramo está %d cometimento atrás de %s
[graphs]
component_loading=A carregar %s…

View file

@ -1399,7 +1399,7 @@ editor.file_is_a_symlink=`«%s» является символической с
editor.filename_is_a_directory=Имя файла «%s» уже используется в качестве каталога в этом репозитории.
editor.file_editing_no_longer_exists=Редактируемый файл «%s» больше не существует в этом репозитории.
editor.file_deleting_no_longer_exists=Удаляемый файл «%s» больше не существует в этом репозитории.
editor.file_changed_while_editing=Содержимое файла изменилось с момента начала редактирования. <a target="_blank" rel="noopener noreferrer" href="%s">Нажмите здесь</a>, чтобы увидеть, что было изменено, или <strong>Зафиксировать изменения снова</strong>, чтобы заменить их.
editor.file_changed_while_editing=Содержимое файла изменилось после того, как он был открыт. <a target="_blank" rel="noopener noreferrer" href="%s">Ознакомьтесь</a> с произошедшими изменениями или <strong>сохраните ещё раз</strong>, чтобы перезаписать их.
editor.file_already_exists=Файл с названием «%s» уже существует в этом репозитории.
editor.commit_empty_file_header=Закоммитить пустой файл
editor.commit_empty_file_text=Файл, который вы собираетесь зафиксировать, пуст. Продолжить?
@ -2237,11 +2237,11 @@ settings.trust_model.default.desc=Использовать фактор дове
settings.trust_model.collaborator=Соучастник
settings.trust_model.collaborator.long=Соучастник: доверять подписям соучастников
settings.trust_model.collaborator.desc=Действительные подписи соучастников этого репозитория будут помечены как «доверенные» (независимо от того, соответствуют ли они автору коммита). В остальных случаях действительные подписи будут помечены как «недоверенные», если подпись соответствует автору коммита, и «не совпадающие», если нет.
settings.trust_model.committer=Коммитер
settings.trust_model.committer.long=Коммитер: доверять подписям, соответствующим коммитерам (соответствует GitHub и требует коммиты, подписанные Forgejo, иметь Forgejo в качестве коммитера)
settings.trust_model.committer=Автор коммита
settings.trust_model.committer.long=Автор коммита: доверять подписям, соответствующим авторам коммитов. Это поведение соответствует GitHub и требует, чтобы коммиты, подписанные Forgejo, имели Forgejo в качестве автора
settings.trust_model.committer.desc=Действительные подписи будут помечены «доверенными», только если они соответствуют автору коммита, в противном случае они будут помечены «не совпадающими». Это заставит Forgejo быть автором подписанных коммитов, а фактический автор будет обозначен в трейлерах Co-Authored-By: и Co-Committed-By: коммита. Ключ Forgejo по умолчанию должен соответствовать пользователю в базе данных.
settings.trust_model.collaboratorcommitter=Соучастник+Коммитер
settings.trust_model.collaboratorcommitter.long=Соучастник+Коммитер: доверять подписям соучастников, которые соответствуют автору коммита
settings.trust_model.collaboratorcommitter=Соучастник и автор коммита
settings.trust_model.collaboratorcommitter.long=Соучастник и автор коммита: доверять подписям соучастников, которые соответствуют автору коммита
settings.trust_model.collaboratorcommitter.desc=Действительные подписи соучастников этого репозитория будут помечены «доверенными», если они соответствуют автору коммита. Действительные подписи будут помечены как «недоверенные», если подпись соответствует автору коммита, и «не совпадающие» впротивном случае. Это заставит Forgejo быть отмеченным в качестве автора подписанного коммита, а фактический автор будет указан в трейлерах Co-Authored-By: и Co-Committed-By: коммита. Ключ Forgejo по умолчанию должен соответствовать пользователю в базе данных.
settings.wiki_delete=Стереть данные вики
settings.wiki_delete_desc=Будьте внимательны! Как только вы удалите вики — пути назад не будет.
@ -2913,6 +2913,9 @@ issues.filter_no_results_placeholder = Попробуйте поискать п
migrate.repo_desc_helper = Оставьте пустым, чтобы скопировать описание из источника
archive.nocomment = Комментирование невозможно, потому что этот репозиторий архивирован.
comment.blocked_by_user = Комментирование невозможно, потому что вы заблокированы его владельцем или автором обсуждения.
sync_fork.branch_behind_few = Эта ветвь отстаёт от %s на %d коммитов
sync_fork.button = Синхронизировать
sync_fork.branch_behind_one = Эта ветвь отстаёт от %s на %d коммит
[graphs]
component_loading_failed = Не удалось загрузить %s
@ -3697,7 +3700,7 @@ no_subscriptions=Нет подписок
default_key=Подписано ключом по умолчанию
error.extract_sign=Не удалось извлечь подпись
error.generate_hash=Не удалось создать хеш коммита
error.no_committer_account=Учётная запись с эл. почтой этого коммитера не найдена
error.no_committer_account=Учётная запись с эл. почтой автора этого коммита не найдена
error.no_gpg_keys_found=Не найден ключ, соответствующий данной подписи
error.not_signed_commit=Неподписанный коммит
error.failed_retrieval_gpg_keys=Не удалось получить ни одного ключа GPG автора коммита
@ -4054,7 +4057,7 @@ union_tooltip = Включает результаты с совпавшими к
union = Обычный
milestone_kind = Найти этапы...
regexp = Регулярное выражение
regexp_tooltip = Интерпретировать поисковый запрос как регулярное выражение
regexp_tooltip = Поисковый запрос будет воспринят как регулярное выражение
[markup]

View file

@ -231,7 +231,7 @@ platform_desc=Forgejo підтверджено працює на вільних
lightweight=Невибагливість
lightweight_desc=Forgejo має низькі вимоги до ресурсів та може працювати на недорогому Raspberry Pi. Заощадьте енергію свого комп'ютера!
license=Відкритий вихідний код
license_desc=Відвідайте <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Приєднайтесь до нас та <a target="_blank" rel="noopener noreferrer" href="%[2]s">зробіть свій внесок</a> до проєкту, щоб зробити його ще краще. Не бійтеся долучитися!
license_desc=Відвідайте <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Приєднуйтесь до нас та <a target="_blank" rel="noopener noreferrer" href="%[2]s">зробіть свій внесок</a>, щоб покращити проєкт ще більше. Не бійтеся долучитися!
install_desc = Просто <a target="_blank" rel="noopener noreferrer" href="%[1]s">запустіть уже зібрану програму</a> для своєї платформи, розгорніть її за допомогою <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a> або встановіть <a target="_blank" rel="noopener noreferrer" href="%[3]s">пакунок</a>.
[install]
@ -299,7 +299,7 @@ disable_gravatar.description=Вимкнути Gravatar або інші стор
federated_avatar_lookup=Увімкнути федеровані аватари
federated_avatar_lookup.description=Увімкнути зовнішні аватари за допомогою Libravatar.
disable_registration=Вимкнути самостійну реєстрацію
disable_registration.description=Тільки адміністратор може створювати нові облікові записи. Наполегливо рекомендуємо залишити реєстрацію вимкненою, якщо ви не збираєтеся розміщувати загальнодоступний екземпляр та сприяти появі величезної кількості спам-акаунтів.
disable_registration.description=Тільки адміністратор може створювати нові облікові записи. Настійно рекомендуємо залишити реєстрацію вимкненою, якщо ви не збираєтеся розміщувати загальнодоступний екземпляр та сприяти появі величезної кількості спам-акаунтів.
allow_only_external_registration.description=Користувачам буде дозволено реєструватись лише через налаштовані сторонні сервіси.
openid_signin=Увімкнути реєстрацію за допомогою OpenID
openid_signin.description=Увімкнути вхід за допомогою OpenID.
@ -2659,6 +2659,9 @@ release.asset_name = Назва ресурсу
release.add_external_asset = Додати зовнішній ресурс
find_file.no_matching = Не знайдено відповідного файлу
commits.search.tooltip = До ключових слів можна додавати префікси «author:», «committer:», «after:» або «before:», наприклад, «revert author:Alice before:2019-01-13».
sync_fork.button = Синхронізувати
sync_fork.branch_behind_one = Ця гілка на %d коміт позаду %s
sync_fork.branch_behind_few = Ця гілка на %d комітів позаду %s
[graphs]
contributors.what = внески

View file

@ -285,7 +285,7 @@ log_root_path=日志路径
log_root_path_helper=日志文件将写入此目录。
optional_title=可选设置
email_title=电子邮设置
email_title=电子邮设置
smtp_addr=SMTP 主机地址
smtp_port=SMTP 端口
smtp_from=电子邮件发件人
@ -322,12 +322,12 @@ install_btn_confirm=立即安装
test_git_failed=无法识别 “git” 命令:%v
sqlite3_not_available=当前 Forgejo 版本不支持 SQLite3。请从 %s 下载官方构建版(注:请勿下载标有 “gobuild” 的版本)。
invalid_db_setting=数据库设置无效:%v
invalid_db_table=数据库表 '%s' 无效: %v
invalid_db_table=数据库表 '%s' 无效:%v
invalid_repo_path=仓库根目录设置无效:%v
invalid_app_data_path=应用数据路径无效: %v
invalid_app_data_path=应用数据路径无效:%v
run_user_not_match=运行用户名不是当前的用户名:%s -> %s
internal_token_failed=生成内部令牌失败: %v
secret_key_failed=生成密钥失败: %v
internal_token_failed=生成内部令牌失败:%v
secret_key_failed=生成密钥失败:%v
save_config_failed=应用配置保存失败:%v
invalid_admin_setting=管理员帐户设置无效:%v
invalid_log_root_path=日志路径无效:%v
@ -354,7 +354,7 @@ app_slogan = 实例标语
app_slogan_helper = 在此处输入您的实例标语。留空则禁用。
[home]
uname_holder=用户名或电子邮
uname_holder=用户名或电子邮件地址
password_holder=密码
switch_dashboard_context=切换控制面板用户
my_repos=仓库列表
@ -479,7 +479,7 @@ password_pwned_err=无法完成对 HaveIBeenPwned 的请求
last_admin=您不能删除最后一个管理员。必须至少保留一个管理员。
change_unconfirmed_email = 如果您在注册时提供了错误的邮箱地址,您可以在下方修改,激活邮件会发送到修改后的邮箱地址。
change_unconfirmed_email_summary = 修改用来接收激活邮件的邮箱地址。
change_unconfirmed_email_error = 无法修改邮箱地址: %v
change_unconfirmed_email_error = 无法修改邮箱地址:%v
tab_signin = 登录
tab_signup = 注册
hint_login = 已经有账户了吗?<a href="%s">立即登录!</a>
@ -537,7 +537,7 @@ issue.in_tree_path=在 %s 中:
release.new.subject=%[2]s 中的 %[1]s 发布了
release.new.text=<b>@%[1]s</b> 于 %[3]s 发布了 %[2]s
release.title=标题: %s
release.title=标题:%s
release.note=注释:
release.downloads=下载:
release.download.zip=源代码ZIP
@ -617,7 +617,7 @@ include_error=`必须包含子字符串 "%s"。`
glob_pattern_error=`匹配模式无效:%s.`
regex_pattern_error=`正则表达式无效:%s.`
username_error=` 只允许包含字母数字字符“0-9”、“a-z”、“A-Z”、破折号“-”、下划线“_”和点“.”)。不能以非字母数字字符开头或结尾,并且不允许连续的非字母数字字符。`
invalid_group_team_map_error=`映射无效: %s`
invalid_group_team_map_error=`映射无效:%s`
unknown_error=未知错误:
captcha_incorrect=验证码不正确。
password_not_match=密码不匹配。
@ -659,7 +659,7 @@ organization_leave_success=您已成功离开组织 %s。
invalid_ssh_key=无法验证您的 SSH 密钥:%s
invalid_gpg_key=无法验证您的 GPG 密钥:%s
invalid_ssh_principal=无效的规则: %s
invalid_ssh_principal=无效的规则:%s
must_use_public_key=您提供的密钥是私钥。不要在任何地方上传您的私钥,请改用您的公钥。
unable_verify_ssh_key=无法验证 SSH 密钥,请仔细检查是否有错误。
auth_failed=授权验证失败:%v
@ -685,7 +685,7 @@ Description = 描述
Pronouns = 代称
Biography = 简历
username_claiming_cooldown = 用户名不能被认领,因为其仍处于保护期。其可以在%[1]s后被认领。
email_domain_is_not_allowed = 用户电子邮的域名<b>%s</b>与EMAIL_DOMAIN_ALLOWLIST或EMAIL_DOMAIN_BLOCKLIST冲突。请确保您正确设置了电子邮件地址。
email_domain_is_not_allowed = 用户电子邮件地址的域名<b>%s</b>与EMAIL_DOMAIN_ALLOWLIST或EMAIL_DOMAIN_BLOCKLIST冲突。请确保您正确设置了电子邮件地址。
[user]
change_avatar=修改头像…
@ -794,7 +794,7 @@ privacy=隐私设置
keep_activity_private=隐藏个人资料页面中的活动
keep_activity_private_popup=您的活动将只对您自己和本实例的管理员可见
lookup_avatar_by_mail=使用电子邮地址查找头像
lookup_avatar_by_mail=使用电子邮地址查找头像
federated_avatar_lookup=查找联合头像
enable_custom_avatar=使用自定义头像
choose_new_avatar=选择新的头像
@ -828,8 +828,8 @@ activations_pending=等待激活
can_not_add_email_activations_pending=有一个待处理的激活请求,请稍等几分钟后再尝试添加新的电子邮件地址。
delete_email=移除
email_deletion=移除电子邮件地址
email_deletion_desc=电子邮箱地址和相关信息将会被删除。使用此电子邮箱地址发送的Git提交将会保留继续
email_deletion_success=您的电子邮地址已被移除。
email_deletion_desc=电子邮件地址和相关信息将会被删除。使用此电子邮件地址发送的Git提交将会保留继续
email_deletion_success=您的电子邮地址已被移除。
theme_update_success=您的主题已更新。
theme_update_error=所选主题不存在。
openid_deletion=移除 OpenID 地址
@ -1459,7 +1459,7 @@ commits.view_path=在历史记录中的此处查看
commit.operations=操作
commit.revert=还原
commit.revert-header=还原: %s
commit.revert-header=还原:%s
commit.revert-content=选择要还原的分支:
commit.cherry-pick=拣选
commit.cherry-pick-header=Cherry-pick%s
@ -2279,7 +2279,7 @@ settings.trust_model.collaborator=协作者
settings.trust_model.collaborator.long=协作者:信任协作者的签名
settings.trust_model.collaborator.desc=此仓库中协作者的有效签名将被标记为「可信」(无论它们是否是提交者),签名只符合提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。
settings.trust_model.committer=提交者
settings.trust_model.committer.long=提交者: 信任与提交者相符的签名(此特性类似 GitHub这会强制采用 Forgejo 作为提交者和签名者)
settings.trust_model.committer.long=提交者:信任与提交者相符的签名(此特性类似 GitHub这会强制采用 Forgejo 作为提交者和签名者)
settings.trust_model.committer.desc=有效签名只有和提交者相匹配才会被标记为“受信任”,否则它们将被标记为“不匹配”。这强制 Forgejo 成为签名提交的提交者,而实际提交者被加上 Co-authored-by和 Co-committed-by的标记。 默认的 Forgejo 密钥必须匹配数据库中的一名用户。
settings.trust_model.collaboratorcommitter=协作者+提交者
settings.trust_model.collaboratorcommitter.long=协作者+提交者:信任协作者同时是提交者的签名
@ -2500,9 +2500,9 @@ settings.protect_branch_name_pattern=受保护的分支名称正则
settings.protect_branch_name_pattern_desc=受保护的分支名称正则。语法请参阅<a href="%s">文档</a> 。如main, release/**
settings.protect_patterns=规则
settings.protect_protected_file_patterns=受保护的文件模式(使用半角分号“;”分隔)
settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用半角分号(“;”)分隔多个模式。 见<a href="%[1]s">%[2]s</a>文档了解模式语法。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用半角分号(“;”)分隔多个模式。 见<a href="%[1]s">%[2]s</a>文档了解模式语法。例如:<code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
settings.protect_unprotected_file_patterns=不受保护的文件模式(使用半角分号“;”分隔)
settings.protect_unprotected_file_patterns_desc=在用户有写权限的情况下允许绕过限制,直接修改设为不保护的文件。如有多个匹配模式,则可用半角分号(“;”)分隔开。见 <a href="%[1]s">%[2]s</a> 的文档以了解匹配模式的格式。例子: <code>.drone.yml</code>、<code>/docs/**/*.txt</code>。
settings.protect_unprotected_file_patterns_desc=在用户有写权限的情况下允许绕过限制,直接修改设为不保护的文件。如有多个匹配模式,则可用半角分号(“;”)分隔开。见 <a href="%[1]s">%[2]s</a> 的文档以了解匹配模式的格式。例子:<code>.drone.yml</code>、<code>/docs/**/*.txt</code>。
settings.add_protected_branch=启用保护
settings.delete_protected_branch=禁用保护
settings.update_protect_branch_success=分支保护规则 %s 更新成功。
@ -2912,6 +2912,9 @@ issues.filter_no_results_placeholder = 尝试调整搜索筛选条件。
migrate.repo_desc_helper = 留空以导入现有描述
archive.nocomment = 您无法评论,因为此仓库已存档。
comment.blocked_by_user = 您无法评论,因为您已被仓库所有者或作者屏蔽。
sync_fork.button = 同步
sync_fork.branch_behind_one = 此分支落后于 %s %d 个提交
sync_fork.branch_behind_few = 此分支落后于 %s %d 个提交
[graphs]
component_loading=正在加载 %s…
@ -3084,11 +3087,11 @@ dashboard.task.process=任务:%[1]s
dashboard.task.cancelled=任务:%[1]s 已取消:%[3]s
dashboard.task.error=任务中的错误:%[1]s%[3]s
dashboard.task.finished=任务:%[2]s 启动的 %[1]s 已完成
dashboard.task.unknown=未知任务: %[1]s
dashboard.task.unknown=未知任务:%[1]s
dashboard.cron.started=已开始计划任务:%[1]s
dashboard.cron.process=计划任务:%[1]s
dashboard.cron.cancelled=定时任务:%[1]s 已取消:%[3]s
dashboard.cron.error=任务中的错误: %s%[3]s
dashboard.cron.error=任务中的错误:%s%[3]s
dashboard.cron.finished=任务:%[1]s 已经完成
dashboard.delete_inactive_accounts=删除所有未激活的帐户
dashboard.delete_inactive_accounts.started=删除所有未激活的账户任务已启动。
@ -3222,7 +3225,7 @@ emails.filter_sort.email_reverse=电子邮件(逆序)
emails.filter_sort.name=用户名
emails.filter_sort.name_reverse=用户名(倒序)
emails.updated=电子邮件已更新
emails.not_updated=无法更新请求的电子邮件地址: %v
emails.not_updated=无法更新请求的电子邮件地址:%v
emails.duplicate_active=此电子邮件地址已被另一个用户激活使用。
emails.change_email_header=更新电子邮件属性
emails.change_email_text=您确定要更新该电子邮件地址吗?
@ -3248,7 +3251,7 @@ repos.lfs_size=LFS 大小
packages.package_manage_panel=软件包管理
packages.total_size=总大小:%s
packages.unreferenced_size=未引用大小: %s
packages.unreferenced_size=未引用大小:%s
packages.cleanup=清理过期数据
packages.cleanup.success=清理过期数据成功
packages.owner=所有者
@ -3291,7 +3294,7 @@ auths.attribute_username=用户名属性
auths.attribute_username_placeholder=置空将使用Forgejo的用户名。
auths.attribute_name=名字属性
auths.attribute_surname=姓氏属性
auths.attribute_mail=电子邮属性
auths.attribute_mail=电子邮件地址属性
auths.attribute_ssh_public_key=SSH公钥属性
auths.attribute_avatar=头像属性
auths.attributes_in_bind=从 bind DN 中拉取属性信息
@ -3345,7 +3348,7 @@ auths.oauth2_group_claim_name=用于提供用户组名称的 Claim 声明名称
auths.oauth2_admin_group=管理员用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
auths.oauth2_restricted_group=受限用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
auths.oauth2_map_group_to_team=映射声明的组到组织团队。(可选 - 要求在上面填写声明的名字)
auths.oauth2_map_group_to_team_removal=如果用户不属于相应的组,同步团队中移除用户
auths.oauth2_map_group_to_team_removal=如果用户不属于相应的组,从同步团队中移除用户
auths.enable_auto_register=允许自动注册
auths.sspi_auto_create_users=自动创建用户
auths.sspi_auto_create_users_helper=允许 SSPI 认证在用户第一次登录时自动创建新账号
@ -3362,7 +3365,7 @@ auths.tips.oauth2.general=OAuth2 认证
auths.tips.oauth2.general.tip=当注册新的 OAuth2 身份验证时,回调/重定向 URL 应该是:
auths.tip.oauth2_provider=OAuth2 提供程序
auths.tip.bitbucket=`在 %s
auths.tip.nextcloud=使用下面的菜单“设置Settings -> 安全Security -> OAuth 2.0 client”在您的实例上注册一个新的 OAuth 客户端。
auths.tip.nextcloud=使用菜单“设置->安全->OAuth 2.0客户端”在您的实例上注册一个新的 OAuth 客户端。
auths.tip.dropbox=在 %s 上创建一个新的应用程序
auths.tip.facebook=`在 %s 注册一个新的应用,并添加产品"Facebook 登录"`
auths.tip.github=在 %s 注册一个 OAuth 应用程序
@ -3386,7 +3389,7 @@ auths.still_in_used=认证源仍在使用。请先解除或者删除使用此认
auths.deletion_success=认证源已经更新。
auths.login_source_exist=认证源 '%s' 已经存在。
auths.login_source_of_type_exist=此类型的认证源已存在。
auths.unable_to_initialize_openid=无法初始化 OpenID Connect 提供商: %s
auths.unable_to_initialize_openid=无法初始化 OpenID Connect 提供商:%s
auths.invalid_openIdConnectAutoDiscoveryURL=无效的 Auto Discovery URL这必须是一个以 http:// 或 https://开头的有效的 URL
config.server_config=服务器配置
@ -3547,7 +3550,7 @@ monitor.process.cancel_notices=中止:<strong>%s</strong>
monitor.process.children=子进程
monitor.queues=队列
monitor.queue=队列: %s
monitor.queue=队列:%s
monitor.queue.name=名称
monitor.queue.type=类型
monitor.queue.exemplar=数据类型
@ -3664,7 +3667,7 @@ raw_minutes=分钟
[dropzone]
default_message=拖放文件或点击此处上传。
invalid_input_type=您不能上传该类型的文件。
file_too_big=文件体积({{filesize}} MB超过了最大允许体积{{maxFilesize}} MB
file_too_big=文件体积({{filesize}} MB超过了最大允许体积{{maxFilesize}} MB
remove_file=移除文件
[notification]
@ -3695,7 +3698,7 @@ error.probable_bad_default_signature=警告虽然默认密钥拥有此ID
[units]
unit=单元
error.no_unit_allowed_repo=您没有被允许访问此仓库的任何单元。
error.unit_not_allowed=您没有权限访问此仓库单元
error.unit_not_allowed=您没有权限访问此仓库单元
[packages]
title=软件包
@ -3722,7 +3725,7 @@ details.project_site=项目站点
details.repository_site=仓库网站
details.documentation_site=文档站点
details.license=许可协议
assets=文件
assets=资源
versions=版本
versions.view_all=查看全部
dependency.id=ID
@ -3819,7 +3822,7 @@ settings.delete.error=删除软件包失败。
owner.settings.cargo.title=Cargo 注册中心索引
owner.settings.cargo.initialize=初始化索引
owner.settings.cargo.initialize.description=使用 Cargo 注册中心时需要一个特殊索引的 Git 仓库。使用此选项将(重新)创建仓库并自动配置它。
owner.settings.cargo.initialize.error=初始化Cargo索引失败 %v
owner.settings.cargo.initialize.error=初始化Cargo索引失败%v
owner.settings.cargo.initialize.success=Cargo索引已经成功创建。
owner.settings.cargo.rebuild=重建索引
owner.settings.cargo.rebuild.description=如果索引与存储的 Cargo 包不同步,重建可能会有用。
@ -3878,11 +3881,11 @@ alt.repository.multiple_groups = 此软件包在多个组中可用。
[secrets]
secrets=密钥
description=Secrets 将被传给特定的 Actions其它情况将不能读取
description=机密将被传给特定的 Action其它情况将不能被读取。
none=还没有密钥。
creation=添加密钥
creation.name_placeholder=不区分大小写,只能包含英文字母、数字或下划线,不能以 GITEA_ 或 GITHUB_ 开头
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
creation.value_placeholder=输入任何内容。开头和结尾的空格都会被省略。
creation.success=您的密钥 '%s' 添加成功。
creation.failed=添加密钥失败。
deletion=删除密钥
@ -3917,7 +3920,7 @@ runners.description=组织描述
runners.labels=标签
runners.last_online=上次在线时间
runners.runner_title=运行器
runners.task_list=最近在此runner上的任务
runners.task_list=最近在此运行器上的任务
runners.task_list.no_tasks=还没有任务。
runners.task_list.run=执行
runners.task_list.status=状态
@ -3932,8 +3935,8 @@ runners.delete_runner=删除运行器
runners.delete_runner_success=运行器删除成功
runners.delete_runner_failed=删除运行器失败
runners.delete_runner_header=确认要删除此运行器
runners.delete_runner_notice=如果一个任务正在运行在此运行器上,它将被终止并标记为失败。它可能会打断正在构建的工作流。
runners.none=无可用的 Runner
runners.delete_runner_notice=如果有任务正在此运行器上运行,它将被终止并标记为失败。这可能会中断正在构建的工作流。
runners.none=无可用的运行器
runners.status.unspecified=未知
runners.status.idle=空闲
runners.status.active=激活
@ -3946,8 +3949,8 @@ runs.all_workflows=所有工作流
runs.commit=提交
runs.scheduled=已计划的
runs.pushed_by=推送者
runs.invalid_workflow_helper=工作流配置文件无效。请检查您的配置文件: %s
runs.no_matching_online_runner_helper=没有匹配标签的在线 runner %s
runs.invalid_workflow_helper=工作流配置文件无效。请检查您的配置文件:%s
runs.no_matching_online_runner_helper=没有匹配标签的在线运行器:%s
runs.actor=操作者
runs.status=状态
runs.actors_no_select=所有操作者
@ -3973,7 +3976,7 @@ variables.creation=添加变量
variables.none=目前还没有变量。
variables.deletion=删除变量
variables.deletion.description=删除变量是永久性的,无法撤消。继续吗?
variables.description=变量将被传给特定的 Actions,其它情况将不能读取
variables.description=变量将被传给特定的 Action,其它情况将不能读取
variables.id_not_exist=ID为 %d 的变量不存在。
variables.edit=编辑变量
variables.deletion.failed=删除变量失败。

View file

@ -206,6 +206,7 @@ buttons.new_table.tooltip = 新增表格
table_modal.header = 新增表格
buttons.indent.tooltip = 使項目縮排一層
buttons.unindent.tooltip = 使項目取消縮排一層
link_modal.header = 新增連結
[filter]
string.asc=A - Z
@ -2857,6 +2858,9 @@ settings.units.units = 功能
diff.git-notes.add = 增加註釋
diff.git-notes.remove-header = 移除註釋
settings.event_pull_request_enforcement = 執行
sync_fork.branch_behind_few = 此分支落後 %s %d 次提交
sync_fork.button = 同步
sync_fork.branch_behind_one = 此分支落後 %s %d 次提交
[graphs]
component_loading = %s載入中…

View file

@ -1,21 +1,4 @@
# Forgejo translations
This directory contains all .INI translations.
## Working on base language
When you work on Forgejo features, you should only modify `locale_en-US.ini`.
* consult https://forgejo.org/docs/next/contributor/localization-english/
* add strings when your change requires doing so
* remove strings when your change renders them unused
## Working on other languages
Translations are done on Codeberg Translate and not via individual pull requests.
* consult https://forgejo.org/docs/next/contributor/localization/
* see the project: https://translate.codeberg.org/projects/forgejo/forgejo/
See [locale_readme.md](../locale_readme.md) for modification instructions.
## Attribution

View file

@ -20,5 +20,8 @@
"themes.names.forgejo-light": "Forgejo světlé",
"themes.names.forgejo-dark": "Forgejo tmavé",
"error.not_found.title": "Stránka nenalezena",
"alert.asset_load_failed": "Nepodařilo se načíst soubory příloh z {path}. Ujistěte se, že jsou dané soubory přístupné."
"alert.asset_load_failed": "Nepodařilo se načíst soubory příloh z {path}. Ujistěte se, že jsou dané soubory přístupné.",
"install.invalid_lfs_path": "Nepodařilo se vytvořit kořen LFS na zvolené cestě: %[1]s",
"alert.range_error": " musí být číslo mezi %[1]s a %[2]s.",
"meta.last_line": "Díky všem přispěvatelům za pomoc!"
}

View file

@ -20,7 +20,6 @@
"error.not_found.title": "Siden blev ikke fundet",
"alert.asset_load_failed": "Kunne ikke indlæse aktivfiler fra {path}. Sørg for, at aktivfilerne er tilgængelige.",
"install.invalid_lfs_path": "Kan ikke oprette LFS-roden på den angivne sti: %[1]s",
"install.lfs_jwt_secret_failed": "Kan ikke generere en LFS JWT-hemmelighed: %[1]s",
"settings.adopt": "Adoptere",
"alert.range_error": " skal være et tal mellem %[1]s og %[2]s."
"alert.range_error": " skal være et tal mellem %[1]s og %[2]s.",
"meta.last_line": "Livet er det dejligste eventyr. - (H.C. Andersen)"
}

View file

@ -18,5 +18,8 @@
"themes.names.forgejo-light": "Forgejo hell",
"themes.names.forgejo-dark": "Forgejo dunkel",
"error.not_found.title": "Seite nicht gefunden",
"alert.asset_load_failed": "Konnte Asset-Dateien nicht von {path} laden. Bitte stelle sicher, dass auf die Asset-Dateien zugegriffen werden kann."
"alert.asset_load_failed": "Konnte Asset-Dateien nicht von {path} laden. Bitte stelle sicher, dass auf die Asset-Dateien zugegriffen werden kann.",
"install.invalid_lfs_path": "Der LFS-Root konnte nicht am angegebenen Pfad erstellt werden: %[1]s",
"alert.range_error": " muss eine Zahl zwischen %[1]s und %[2]s sein.",
"meta.last_line": "Vielen Dank für die Übersetzung von Forgejo! Diese Zeile wird von den Benutzern nicht gesehen, dient aber anderen Zwecken im Übersetzungsmanagement. Du kannst eine lustige Tatsache in der Übersetzung platzieren, anstatt sie zu übersetzen."
}

View file

@ -1,12 +1,16 @@
{
"repo.pulls.merged_title_desc": "yhdistetty %[1]d committia lähteestä <code>%[2]s</code> kohteeseen <code>%[3]s</code> %[4]s",
"repo.pulls.title_desc": "haluaa yhdistää %[1]d committia lähteestä <code>%[2]s</code> kohteeseen <code id=\"%[4]s\">%[3]s</code>",
"search.milestone_kind": "Etsi merkkipaaluja...",
"search.milestone_kind": "Etsi merkkipaaluja",
"home.welcome.no_activity": "Ei toimintaa",
"incorrect_root_url": "Tämä Forgejo-instanssi on määritetty toimimaan osoitteessa \"%s\". Tarkastelet tällä hetkellä Forgejoa eri URL-osoitteen kautta, mikä saattaa aiheuttaa sovelluksen osien toimimattomuutta. Virallinen URL-osoite on Forgejo-ylläpitäjien hallinnoima ROOT_URL-asetus app.ini -tiedostossa.",
"themes.names.forgejo-auto": "Forgejo (käyttöjärjestelmän määrittelemä teema)",
"home.welcome.activity_hint": "Syötteelläsi ei ole vielä mitään. Toimintasi ja toiminta repositorioissa joita seuraat ilmaantuu tälle sivulle.",
"home.explore_repos": "Tutki repositorioita",
"home.explore_users": "Tutki käyttäjiä",
"home.explore_orgs": "Tutki organisaatioita"
"home.explore_orgs": "Tutki organisaatioita",
"error.not_found.title": "Sivua ei löytynyt",
"themes.names.forgejo-light": "Forgejo, vaalea",
"themes.names.forgejo-dark": "Forgejo, tumma",
"alert.range_error": " täytyy olla numero välillä %[1]s ja %[2]s."
}

View file

@ -17,5 +17,9 @@
"home.explore_repos": "Tuklasin ang mga repositoryo",
"home.explore_users": "Tuklasin ang mga user",
"home.explore_orgs": "Tuklasin ang mga organisasyon",
"error.not_found.title": "Hindi nahanap ang pahina"
"error.not_found.title": "Hindi nahanap ang pahina",
"alert.asset_load_failed": "Nabigong i-load ang mga asset file mula sa {path}. Siguraduhin na maa-access ang mga asset file.",
"install.invalid_lfs_path": "Nabigong gawin ang LFS root sa tinakdang path: %[1]s",
"alert.range_error": " dapat ay numero sa pagitan ng %[1]s at %[2]s.",
"meta.last_line": "Sayori... I love you. — MC from Doki Doki Literature Club"
}

View file

@ -20,5 +20,8 @@
"themes.names.forgejo-light": "Forgejo gaišais",
"themes.names.forgejo-dark": "Forgejo tumšais",
"error.not_found.title": "Lapa nav atrasta",
"alert.asset_load_failed": "Neizdevās ielādēt līdzekļu datnes no {path}. Lūgums pārliecināties, ka līdzekļu datnēm var piekļūt."
"alert.asset_load_failed": "Neizdevās ielādēt līdzekļu datnes no {path}. Lūgums pārliecināties, ka līdzekļu datnēm var piekļūt.",
"install.invalid_lfs_path": "Nav iespējams izveidot LFS pamatmapi norādītajā ceļā: %[1]s",
"alert.range_error": " jābūt skaitlin starp %[1]s un %[2]s.",
"meta.last_line": "Šis žagaru saišķis nav mans žagaru saišķis."
}

View file

@ -19,8 +19,7 @@
"themes.names.forgejo-auto": "Forgejo (Systeem-Thema nagahn)",
"error.not_found.title": "Sied nich funnen",
"alert.asset_load_failed": "Kunn Objekt-Dateien ut {path} nich laden. Bidde wees wiss, dat up de Objekt-Dateien togriepen worden kann.",
"install.lfs_jwt_secret_failed": "Kunn dat LFS-JWT-Geheemst nich maken: %[1]s",
"settings.adopt": "Övernehmen",
"install.invalid_lfs_path": "Kunn de LFS-Ruut an de angeven Padd nich maken: %[1]s",
"alert.range_error": " mutt eene Tahl tüsken %[1]s un %[2]s wesen."
"alert.range_error": " mutt eene Tahl tüsken %[1]s un %[2]s wesen.",
"meta.last_line": "Moin!"
}

View file

@ -19,7 +19,7 @@
"themes.names.forgejo-dark": "Forgejo donker",
"error.not_found.title": "Pagina niet gevonden",
"alert.asset_load_failed": "Het laden van asset-bestanden van {path} is mislukt. Controleer of de asset-bestanden toegankelijk zijn.",
"settings.adopt": "Adopteer",
"install.invalid_lfs_path": "Kan de LFS-root niet aanmaken op het opgegeven pad: %[1]s",
"install.lfs_jwt_secret_failed": "Kan geen LFS JWT-geheim genereren: %[1]s"
"alert.range_error": " moet een getal zijn tussen %[1]s en %[2]s.",
"meta.last_line": "Wist je dat de paprika, behalve in de bekende kleuren rood, geel, oranje en groen, ook in de kleuren wit, paars, lila, muntgroen en bruin voorkomt."
}

View file

@ -20,5 +20,8 @@
"themes.names.forgejo-light": "Forgejo claro",
"themes.names.forgejo-dark": "Forgejo escuro",
"error.not_found.title": "Página não encontrada",
"alert.asset_load_failed": "Não foi possível carregar arquivos de assets de {path}. Por favor, certifique-se que os arquivos podem ser acessados."
"alert.asset_load_failed": "Não foi possível carregar arquivos de assets de {path}. Por favor, certifique-se que os arquivos podem ser acessados.",
"install.invalid_lfs_path": "Não foi possível criar um root LFS no caminho especificado: %[1]s",
"alert.range_error": " deve ser um número entre %[1]s e %[2]s.",
"meta.last_line": "real hot girl shit"
}

View file

@ -19,5 +19,9 @@
"themes.names.forgejo-auto": "Forgejo (segue o tema do sistema)",
"themes.names.forgejo-light": "Forgejo claro",
"themes.names.forgejo-dark": "Forgejo escuro",
"error.not_found.title": "Página não encontrada"
"error.not_found.title": "Página não encontrada",
"alert.asset_load_failed": "Falha ao carregar ficheiros de recurso de {path}. Certifique-se de que os ficheiros de recurso podem ser acedidos.",
"install.invalid_lfs_path": "Não foi possível criar a raiz LFS no caminho especificado: %[1]s",
"alert.range_error": " deve ser um número entre %[1]s e %[2]s.",
"meta.last_line": "Se programarem em Python, por favor usem o uv e Ruff (e estejam atentos ao Red Knot no repositório do Ruff). E vejam também mise-en-place (https://mise.jdx.dev)."
}

View file

@ -19,5 +19,9 @@
"themes.names.forgejo-light": "Forgejo светлая",
"themes.names.forgejo-auto": "Forgejo как в системе",
"themes.names.forgejo-dark": "Forgejo тёмная",
"error.not_found.title": "Страница не найдена"
"error.not_found.title": "Страница не найдена",
"alert.asset_load_failed": "Не удалось получить ресурсы из {path}. Убедитесь, что файлы ресурсов доступны.",
"install.invalid_lfs_path": "Не удалось расположить корень LFS по указанному пути: %[1]s",
"alert.range_error": " - число должно быть в диапазоне от %[1]s-%[2]s.",
"meta.last_line": "Спасибо Forgejo за такую возможность :3. Интересный факт: мне не удавалось вставить свой вклад в перевод Forgejo и это первый раз, когда у меня это вышло. ❤️."
}

View file

@ -22,7 +22,6 @@
"error.not_found.title": "Сторінку не знайдено",
"alert.asset_load_failed": "Не вдалося завантажити файли ресурсів з {path}. Переконайтеся, що до файлів ресурсів є доступ.",
"install.invalid_lfs_path": "Не вдалося створити корінь LFS за вказаним шляхом: %[1]s",
"settings.adopt": "Прийняти",
"install.lfs_jwt_secret_failed": "Не вдалося створити секрет LFS JWT: %[1]s",
"alert.range_error": " має бути числом від %[1]s до %[2]s."
"alert.range_error": " має бути числом від %[1]s до %[2]s.",
"meta.last_line": "Не зливай злий запити на злиття — зіллється зле."
}

View file

@ -12,8 +12,8 @@
"themes.names.forgejo-light": "Forgejo 浅色",
"themes.names.forgejo-dark": "Forgejo 深色",
"error.not_found.title": "页面不存在",
"alert.asset_load_failed": "无法从 {path} 加载资源文件。请确保可以访问资源文件。",
"install.lfs_jwt_secret_failed": "无法生成 LFS JWT 密钥:%[1]s",
"alert.asset_load_failed": "无法从 {path} 加载资源文件。请确保资源文件可被访问。",
"install.invalid_lfs_path": "无法在指定路径创建 LFS 根目录:%[1]s",
"alert.range_error": " 必须是一个介于 %[1]s 和 %[2]s 之间的数字。"
"alert.range_error": " 必须是一个介于 %[1]s 和 %[2]s 之间的数字。",
"meta.last_line": "感谢各位对Forgejo翻译的支持和帮助不需要翻译这个。"
}

50
options/locale_readme.md Normal file
View file

@ -0,0 +1,50 @@
# Forgejo translations
All translations are stored in directories `locale` and `locale_next`.
`locale` is a historical directory that contains translations in INI format. Forgejo inherited it from Gitea, and Gitea inherited it from Gogs.
Because the INI format had many issues and prevented good translatability, in early 2025 Forgejo started switching to a new format - `go-i18n`+`json`.
## Working on base language
Here are some tips:
* when working on non-i18n changes, only change `en-US` files
* non-base files are normally modified through Weblate. We appreciate the intention to provide localization for the change you're working on, however, modifying those files leads to merge conflicts with Weblate that aren't easy to resolve. [Learn about translating Forgejo](#working-on-other-languages).
* when new strings are added, it's preferred that they're added to `locale_en-US.json`
* when strings are modified in `locale_en-US.ini`, it's preferred that they stay here because moving them around is complicated
* make sure to remove strings if your change renders them unused
* consult https://forgejo.org/docs/next/contributor/localization-english/
### JSON translations
It is preferred that all new strings added to Forgejo UI are added to the JSON translations instead of INI.
Even though Forgejo parser supports nested sections, linters and Weblate do not. Because of this, most strings need to have all sections flattened into their keys like so:
```json
"some.nested.section.key": "UI text"
```
However, plural variations of a string, if it has any, are stored in a nested dictionary:
```json
"some.nested.section.key": {
"one": "%d comment",
"other": "%d comments"
}
```
> [!IMPORTANT]
> Please avoid adding unnecessary sections to the keys. Sections like `repo` are vague and represent a large part of the codebase. Keep the sections scoped to where or how the strings are really used, like `user_settings` or `error`.
> [!TIP]
> Due to the flat sections, you can easily find both JSON strings and their usages in the codebase by grepping an entire key.
> [!TIP]
> 3rd party software can determine whether string has plural variations or not from type of it's value in `en-US.json`.
## Working on other languages
Translations are done on Codeberg Translate and not via individual pull requests.
* consult https://forgejo.org/docs/next/contributor/localization/
* see the project: https://translate.codeberg.org/projects/forgejo/

1097
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -16,10 +16,10 @@
"@primer/octicons": "19.14.0",
"ansi_up": "6.0.5",
"asciinema-player": "3.8.2",
"chart.js": "4.4.5",
"chart.js": "4.4.9",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
"clippie": "4.1.1",
"clippie": "4.1.6",
"css-loader": "7.0.0",
"dayjs": "1.11.12",
"dropzone": "6.0.0-beta.2",
@ -30,7 +30,7 @@
"htmx.org": "1.9.12",
"idiomorph": "0.3.0",
"jquery": "3.7.1",
"katex": "0.16.21",
"katex": "0.16.22",
"mermaid": "11.6.0",
"mini-css-extract-plugin": "2.9.2",
"minimatch": "10.0.1",
@ -55,7 +55,7 @@
"vue-chartjs": "5.3.1",
"vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.98.0",
"webpack": "5.99.6",
"webpack-cli": "6.0.1",
"wrap-ansi": "9.0.0"
},
@ -91,6 +91,7 @@
"license-checker-rseidelsohn": "4.4.2",
"markdownlint-cli": "0.44.0",
"postcss-html": "1.8.0",
"sharp": "0.34.1",
"stylelint": "16.17.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.11",

14
poetry.lock generated
View file

@ -130,17 +130,17 @@ six = ">=1.13.0"
[[package]]
name = "json5"
version = "0.10.0"
version = "0.12.0"
description = "A Python implementation of the JSON5 data format."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"},
{file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"},
{file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"},
{file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"},
]
[package.extras]
dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"]
dev = ["build (==1.2.2.post1)", "coverage (==7.5.4)", "coverage (==7.8.0)", "mypy (==1.14.1)", "mypy (==1.15.0)", "pip (==25.0.1)", "pylint (==3.2.7)", "pylint (==3.3.6)", "ruff (==0.11.2)", "twine (==6.1.0)", "uv (==0.6.11)"]
[[package]]
name = "pathspec"
@ -393,13 +393,13 @@ telegram = ["requests"]
[[package]]
name = "typing-extensions"
version = "4.13.0"
version = "4.13.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"},
{file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"},
{file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
{file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
]
[[package]]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

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