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": { "features": {
// installs nodejs into container // installs nodejs into container
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "20" "version": "22"
}, },
"ghcr.io/devcontainers/features/git-lfs:1.2.3": {}, "ghcr.io/devcontainers/features/git-lfs:1.2.3": {},
"ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": { "ghcr.io/devcontainers/features/python:1": {
"version": "3.12" "version": "3.13"
}, },
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
}, },

View file

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

View file

@ -28,7 +28,7 @@ jobs:
runs-on: docker runs-on: docker
container: container:
image: data.forgejo.org/renovate/renovate:39.222.1 image: data.forgejo.org/renovate/renovate:39.252.0
steps: steps:
- name: Load renovate repo cache - 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 AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1 # renovate: datasource=go EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1 # renovate: datasource=go
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 # 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 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 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 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go 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 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 DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.32.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.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 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/ # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
@ -1017,8 +1017,7 @@ generate-gomock:
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules
npm install --no-save fabric@6 imagemin-zopfli@7 node tools/generate-images.js
node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
generate-manpage: generate-manpage:

File diff suppressed because one or more lines are too long

View file

@ -6,6 +6,7 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings"
auth_model "forgejo.org/models/auth" auth_model "forgejo.org/models/auth"
"forgejo.org/models/db" "forgejo.org/models/db"
@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
Name: "access-token", Name: "access-token",
Usage: "Generate access token for the user", 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{ &cli.BoolFlag{
Name: "restricted", Name: "restricted",
Usage: "Make a restricted user account", Usage: "Make a restricted user account",
@ -157,23 +168,40 @@ func runCreateUser(c *cli.Context) error {
IsRestricted: restricted, 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 { if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err) return fmt.Errorf("CreateUser: %w", err)
} }
fmt.Printf("New user '%s' has been successfully created!\n", username)
if c.Bool("access-token") { // create the access token
t := &auth_model.AccessToken{ if accessTokenScope != "" {
Name: "gitea-admin", t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
UID: u.ID,
}
if err := auth_model.NewAccessToken(ctx, t); err != nil { if err := auth_model.NewAccessToken(ctx, t); err != nil {
return err return err
} }
fmt.Printf("Access token was successfully created... %s\n", t.Token) fmt.Printf("Access token was successfully created... %s\n", t.Token)
} }
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil return nil
} }

View file

@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "scopes", Name: "scopes",
Value: "", Value: "all",
Usage: "Comma separated list of scopes to apply to access token", Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
}, },
}, },
Action: runGenerateAccessToken, Action: runGenerateAccessToken,
@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
func runGenerateAccessToken(c *cli.Context) error { func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") { 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() ctx, cancel := installSignals()
@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
if err != nil { if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err) 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 t.Scope = accessTokenScope
// create the token // create the token

View file

@ -54,8 +54,8 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p altTLSALPNPort = p
} }
magic := certmagic.NewDefault() certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
// Try to use private CA root if provided, otherwise defaults to system's trust // Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool var certPool *x509.CertPool
if setting.AcmeCARoot != "" { 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) 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, CA: setting.AcmeURL,
TrustedRoots: certPool, TrustedRoots: certPool,
Email: setting.AcmeEmail, Email: setting.AcmeEmail,
@ -75,7 +76,11 @@ func runACME(listenAddr string, m http.Handler) error {
ListenHost: setting.HTTPAddr, ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort, AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort, AltHTTPPort: altHTTPPort,
}) }
magic := certmagic.NewDefault()
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
magic.Issuers = []certmagic.Issuer{myACME} 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 ;; 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 ;; 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 ;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. ;; 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. ;; 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 ;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) ;; 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) ;; 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 ;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"} SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
fi 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 if [ -d /etc/ssh ]; then
SSH_PORT=${SSH_PORT:-"22"} \ SSH_PORT=${SSH_PORT:-"22"} \
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \ 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/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2
github.com/alecthomas/chroma/v2 v2.16.0 github.com/alecthomas/chroma/v2 v2.16.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb 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/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/chi-middleware/proxy v1.1.1
github.com/djherbis/buffer v1.2.0 github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1 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/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap v1.2.1
github.com/felixge/fgprof v0.9.5 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/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 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/jhillyerd/enmime/v2 v2.1.0
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.17.11 github.com/klauspost/compress v1.18.0
github.com/klauspost/cpuid/v2 v2.2.10 github.com/klauspost/cpuid/v2 v2.2.10
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/markbates/goth v1.80.0 github.com/markbates/goth v1.80.0
github.com/mattn/go-isatty v0.0.20 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/meilisearch/meilisearch-go v0.31.0
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.27 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/msteinert/pam/v2 v2.0.0
github.com/nektos/act v0.2.52 github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0 github.com/niklasfasching/go-org v1.7.0
@ -100,14 +100,14 @@ require (
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go v0.126.0 gitlab.com/gitlab-org/api/client-go v0.126.0
go.uber.org/mock v0.5.0 go.uber.org/mock v0.5.1
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.37.0
golang.org/x/image v0.25.0 golang.org/x/image v0.26.0
golang.org/x/net v0.38.0 golang.org/x/net v0.39.0
golang.org/x/oauth2 v0.28.0 golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.13.0
golang.org/x/sys v0.31.0 golang.org/x/sys v0.32.0
golang.org/x/text v0.23.0 golang.org/x/text v0.24.0
google.golang.org/protobuf v1.36.4 google.golang.org/protobuf v1.36.4
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0 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/grpc-gcp-go/grpcgcp v1.5.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.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/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/brotli v1.1.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blevesearch/bleve_index_api v1.1.12 // indirect github.com/blevesearch/bleve_index_api v1.2.7 // indirect
github.com/blevesearch/geo v0.1.20 // 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/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect github.com/blevesearch/mmap-go v1.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/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.0.10 // indirect github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.3.10 // indirect github.com/blevesearch/zapx/v11 v11.4.1 // indirect
github.com/blevesearch/zapx/v12 v12.3.10 // indirect github.com/blevesearch/zapx/v12 v12.4.1 // indirect
github.com/blevesearch/zapx/v13 v13.3.10 // indirect github.com/blevesearch/zapx/v13 v13.4.1 // indirect
github.com/blevesearch/zapx/v14 v14.3.10 // indirect github.com/blevesearch/zapx/v14 v14.4.1 // indirect
github.com/blevesearch/zapx/v15 v15.3.16 // indirect github.com/blevesearch/zapx/v15 v15.4.1 // indirect
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b // indirect github.com/blevesearch/zapx/v16 v16.2.2 // indirect
github.com/boombuler/barcode v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect
@ -214,12 +214,12 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // 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/mailru/easyjson v0.9.0 // indirect
github.com/markbates/going v1.0.3 // indirect github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.16 // 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/miekg/dns v1.1.63 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // 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/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/blake3 v0.2.4 // 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.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.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/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 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= 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 h1:cSXom2MoKJ9KPPw29RoZtHvUETY4F4n/kXl8m9btnQ0=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2/go.mod h1:JitQWJ8JuV4Y87l8VsHiiwhb3cgdyn68mX40s7NT6PA= 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= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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.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.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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 h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= 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.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4=
github.com/blevesearch/bleve/v2 v2.4.4/go.mod h1:fa2Eo6DP7JR+dMFpQe+WiZXINKSunh7WBtlDGbolKXk= github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk=
github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY= github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY=
github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8= 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 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= 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.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
github.com/blevesearch/go-faiss v1.0.24/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= 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 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= 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 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= 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 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= 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.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o=
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/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= 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/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 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= 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 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= 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.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk= github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA=
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs=
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s= github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ=
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II=
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8= github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE=
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8=
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU= github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE=
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms=
github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE= github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g=
github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y=
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b h1:ju9Az5YgrzCeK3M1QwvZIpxYhChkXp7/L0RhDYsxXoE= github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk=
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b/go.mod h1:BlrYNpOu4BvVRslmIG+rLtKhmjIaRhIbG8sb9scGTwI= 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.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-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1 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/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 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM= 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.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs= 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 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= 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= 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/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.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.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 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= 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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 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.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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 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.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/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/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 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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 v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= 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 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI=
github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk= 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= 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 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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.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.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY=
github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g= 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.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v3 v3.1.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= 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 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= 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/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 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= 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.90 h1:TmSj1083wtAD0kEYTx7a5pFsv3iRYMsOJ6A4crjA1lE=
github.com/minio/minio-go/v7 v7.0.88/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg= 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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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= 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= 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 h1:VV5TdkF6pMbEdFGvbR2CwEgJwg6qdg1u3bj5eD2tiWk=
gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE= gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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/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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 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.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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 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-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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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-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-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.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.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= 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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-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.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 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.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.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.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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.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.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.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 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.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.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.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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-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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= 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 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 // InsertRun inserts a run
// The title will be cut off at 255 characters if it's longer than 255 characters. // 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 { func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {

View file

@ -5,7 +5,6 @@ package actions
import ( import (
"context" "context"
"fmt"
"time" "time"
"forgejo.org/models/db" "forgejo.org/models/db"
@ -119,27 +118,6 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
return committer.Commit() 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 { type FindScheduleOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64

View file

@ -17,10 +17,8 @@ import (
"forgejo.org/modules/timeutil" "forgejo.org/modules/timeutil"
"forgejo.org/modules/util" "forgejo.org/modules/util"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
lru "github.com/hashicorp/golang-lru/v2" lru "github.com/hashicorp/golang-lru/v2"
"github.com/nektos/act/pkg/jobparser" "github.com/nektos/act/pkg/jobparser"
"google.golang.org/protobuf/types/known/timestamppb"
"xorm.io/builder" "xorm.io/builder"
) )
@ -337,140 +335,6 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
return err 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) { func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, limit int) ([]*ActionTask, error) {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
@ -481,13 +345,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
Find(&tasks) 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 { func logFileName(repoFullName string, taskID int64) string {
ret := fmt.Sprintf("%s/%02x/%d.log", repoFullName, taskID%256, taskID) 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 // OK we should try the default key
gpgSettings := git.GPGSettings{ gpgSettings := git.GPGSettings{
Sign: true, Sign: true,

View file

@ -12,8 +12,10 @@ import (
"forgejo.org/models/db" "forgejo.org/models/db"
user_model "forgejo.org/models/user" user_model "forgejo.org/models/user"
"forgejo.org/modules/log" "forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/42wim/sshsig" "github.com/42wim/sshsig"
"golang.org/x/crypto/ssh"
) )
// ParseObjectWithSSHSignature check if signature is good against keystore. // 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{ return &ObjectVerification{
CommittingUser: committer, CommittingUser: committer,
Verified: false, Verified: false,

View file

@ -4,6 +4,7 @@
package asymkey package asymkey
import ( import (
"os"
"testing" "testing"
"forgejo.org/models/db" "forgejo.org/models/db"
@ -15,6 +16,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
) )
func TestParseCommitWithSSHSignature(t *testing.T) { func TestParseCommitWithSSHSignature(t *testing.T) {
@ -150,4 +152,43 @@ muPLbvEduU+Ze/1Ol1pgk=
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason) assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
assert.Equal(t, sshKey, commitVerification.SigningSSHKey) 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 return bitmap.toScope(), nil
} }
func (s AccessTokenScope) HasPermissionScope() bool {
return s != "" && s != AccessTokenScopePublicOnly
}
// PublicOnly checks if this token scope is limited to public resources // PublicOnly checks if this token scope is limited to public resources
func (s AccessTokenScope) PublicOnly() (bool, error) { func (s AccessTokenScope) PublicOnly() (bool, error) {
bitmap, err := s.parse() 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 // SPDX-License-Identifier: MIT
package forgefed package forgefed
@ -19,18 +19,22 @@ type FederationHost struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"` HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"`
NodeInfo NodeInfo `xorm:"extends 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"` LatestActivity time.Time `xorm:"NOT NULL"`
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
KeyID sql.NullString `xorm:"key_id UNIQUE"` KeyID sql.NullString `xorm:"key_id UNIQUE"`
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"` 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. // 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{ result := FederationHost{
HostFqdn: strings.ToLower(hostFqdn), HostFqdn: strings.ToLower(hostFqdn),
NodeInfo: nodeInfo, NodeInfo: nodeInfo,
HostPort: port,
HostSchema: schema,
} }
if valid, err := validation.IsValid(result); !valid { if valid, err := validation.IsValid(result); !valid {
return FederationHost{}, err return FederationHost{}, err
@ -43,6 +47,8 @@ func (host FederationHost) Validate() []string {
var result []string var result []string
result = append(result, validation.ValidateNotEmpty(host.HostFqdn, "HostFqdn")...) result = append(result, validation.ValidateNotEmpty(host.HostFqdn, "HostFqdn")...)
result = append(result, validation.ValidateMaxLen(host.HostFqdn, 255, "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()...) result = append(result, host.NodeInfo.Validate()...)
if host.HostFqdn != strings.ToLower(host.HostFqdn) { if host.HostFqdn != strings.ToLower(host.HostFqdn) {
result = append(result, fmt.Sprintf("HostFqdn has to be lower case but was: %v", 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 ( import (
"context" "context"
"fmt" "fmt"
"strings"
"forgejo.org/models/db" "forgejo.org/models/db"
"forgejo.org/modules/validation" "forgejo.org/modules/validation"
@ -44,8 +43,18 @@ func findFederationHostFromDB(ctx context.Context, searchKey, searchValue string
return host, nil return host, nil
} }
func FindFederationHostByFqdn(ctx context.Context, fqdn string) (*FederationHost, error) { func FindFederationHostByFqdnAndPort(ctx context.Context, fqdn string, port uint16) (*FederationHost, error) {
return findFederationHostFromDB(ctx, "host_fqdn=?", strings.ToLower(fqdn)) 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) { func FindFederationHostByKeyID(ctx context.Context, keyID string) (*FederationHost, error) {

View file

@ -18,6 +18,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo", SoftwareName: "forgejo",
}, },
LatestActivity: time.Now(), LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
} }
if res, err := validation.IsValid(sut); !res { if res, err := validation.IsValid(sut); !res {
t.Errorf("sut should be valid but was %q", err) t.Errorf("sut should be valid but was %q", err)
@ -29,6 +31,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo", SoftwareName: "forgejo",
}, },
LatestActivity: time.Now(), LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
} }
if res, _ := validation.IsValid(sut); res { if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: HostFqdn empty") t.Errorf("sut should be invalid: HostFqdn empty")
@ -40,6 +44,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo", SoftwareName: "forgejo",
}, },
LatestActivity: time.Now(), LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
} }
if res, _ := validation.IsValid(sut); res { if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: HostFqdn too long (len=256)") 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", HostFqdn: "host.do.main",
NodeInfo: NodeInfo{}, NodeInfo: NodeInfo{},
LatestActivity: time.Now(), LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
} }
if res, _ := validation.IsValid(sut); res { if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: NodeInfo invalid") t.Errorf("sut should be invalid: NodeInfo invalid")
@ -60,6 +68,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo", SoftwareName: "forgejo",
}, },
LatestActivity: time.Now().Add(1 * time.Hour), LatestActivity: time.Now().Add(1 * time.Hour),
HostPort: 443,
HostSchema: "https",
} }
if res, _ := validation.IsValid(sut); res { if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: Future timestamp") t.Errorf("sut should be invalid: Future timestamp")
@ -71,6 +81,8 @@ func Test_FederationHostValidation(t *testing.T) {
SoftwareName: "forgejo", SoftwareName: "forgejo",
}, },
LatestActivity: time.Now(), LatestActivity: time.Now(),
HostPort: 443,
HostSchema: "https",
} }
if res, _ := validation.IsValid(sut); res { if res, _ := validation.IsValid(sut); res {
t.Errorf("sut should be invalid: HostFqdn lower case") 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), NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser),
// v28 -> v29 // v28 -> v29
NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation), 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. // GetCurrentDBVersion returns the current Forgejo database version.

View file

@ -7,8 +7,8 @@ import "xorm.io/xorm"
func AddHashBlake2bToPackageBlob(x *xorm.Engine) error { func AddHashBlake2bToPackageBlob(x *xorm.Engine) error {
type PackageBlob struct { type PackageBlob struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
HashBlake2b string HashBlake2b string `xorm:"hash_blake2b char(128) UNIQUE(blake2b) INDEX"`
} }
return x.Sync(&PackageBlob{}) 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{} existing := &PackageBlob{}
has, err := e.Where(builder.Eq{ has, err := e.Where(builder.And(
"size": pb.Size, builder.Eq{
"hash_md5": pb.HashMD5, "size": pb.Size,
"hash_sha1": pb.HashSHA1, "hash_md5": pb.HashMD5,
"hash_sha256": pb.HashSHA256, "hash_sha1": pb.HashSHA1,
"hash_sha512": pb.HashSHA512, "hash_sha256": pb.HashSHA256,
"hash_blake2b": pb.HashBlake2b, "hash_sha512": pb.HashSHA512,
}).Get(existing) },
builder.Or(
builder.Eq{"hash_blake2b": pb.HashBlake2b},
builder.IsNull{"hash_blake2b"},
),
)).Get(existing)
if err != nil { if err != nil {
return nil, false, err 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" "forgejo.org/models/unittest"
user_model "forgejo.org/models/user" 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" "github.com/stretchr/testify/require"
) )
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
func prepareExamplePackage(t *testing.T) *packages_model.Package { func prepareExamplePackage(t *testing.T) *packages_model.Package {
require.NoError(t, unittest.PrepareTestDatabase()) require.NoError(t, unittest.PrepareTestDatabase())

View file

@ -4,13 +4,10 @@
package user package user
import ( import (
"context"
"fmt" "fmt"
"net/url" "net/url"
"forgejo.org/models/db"
"forgejo.org/modules/setting" "forgejo.org/modules/setting"
"forgejo.org/modules/validation"
) )
// APActorID returns the IRI to the api endpoint of the user // 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 { func (u *User) APActorKeyID() string {
return u.APActorID() + "#main-key" 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 // 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 { func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
if u.IsGhost() { if u.IsGhost() || u.ID <= 0 {
return avatars.DefaultAvatarLink() return avatars.DefaultAvatarLink()
} }

View file

@ -1,30 +1,30 @@
// Copyright 2024 The Forgejo Authors. All rights reserved. // Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package user package user
import ( import (
"context"
"database/sql" "database/sql"
"forgejo.org/models/db"
"forgejo.org/modules/validation" "forgejo.org/modules/validation"
) )
type FederatedUser struct { type FederatedUser struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"` UserID int64 `xorm:"NOT NULL"`
ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"` ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"` FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
KeyID sql.NullString `xorm:"key_id UNIQUE"` KeyID sql.NullString `xorm:"key_id UNIQUE"`
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"` 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{ result := FederatedUser{
UserID: userID, UserID: userID,
ExternalID: externalID, ExternalID: externalID,
FederationHostID: federationHostID, FederationHostID: federationHostID,
NormalizedOriginalURL: normalizedOriginalURL,
} }
if valid, err := validation.IsValid(result); !valid { if valid, err := validation.IsValid(result); !valid {
return FederatedUser{}, err return FederatedUser{}, err
@ -32,30 +32,6 @@ func NewFederatedUser(userID int64, externalID string, federationHostID int64) (
return result, nil 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 { func (user FederatedUser) Validate() []string {
var result []string var result []string
result = append(result, validation.ValidateNotEmpty(user.UserID, "UserID")...) result = append(result, validation.ValidateNotEmpty(user.UserID, "UserID")...)

View file

@ -1,6 +1,6 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea 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 // SPDX-License-Identifier: MIT
package user package user
@ -135,9 +135,6 @@ type User struct {
AvatarEmail string `xorm:"NOT NULL"` AvatarEmail string `xorm:"NOT NULL"`
UseCustomAvatar bool UseCustomAvatar bool
// For federation
NormalizedFederatedURI string
// Counters // Counters
NumFollowers int NumFollowers int
NumFollowing int `xorm:"NOT NULL DEFAULT 0"` 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() return committer.Commit()
} }
func FindFederatedUser(ctx context.Context, externalID string, func FindFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) {
federationHostID int64,
) (*User, *FederatedUser, error) {
federatedUser := new(FederatedUser) federatedUser := new(FederatedUser)
user := new(User) user := new(User)
has, err := db.GetEngine(ctx).Where("external_id=? and federation_host_id=?", externalID, federationHostID).Get(federatedUser) 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 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 { func DeleteFederatedUser(ctx context.Context, userID int64) error {
_, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID}) _, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID})
return err return err

View file

@ -91,7 +91,7 @@ func TestWebhook_EventsArray(t *testing.T) {
func TestCreateWebhook(t *testing.T) { func TestCreateWebhook(t *testing.T) {
hook := &Webhook{ hook := &Webhook{
RepoID: 3, RepoID: 3,
URL: "www.example.com/unit_test", URL: "https://www.example.com/unit_test",
ContentType: ContentTypeJSON, ContentType: ContentTypeJSON,
Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`, 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 // SPDX-License-Identifier: MIT
package forgefed package forgefed
@ -6,6 +6,7 @@ package forgefed
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"strconv"
"strings" "strings"
"forgejo.org/modules/validation" "forgejo.org/modules/validation"
@ -15,13 +16,14 @@ import (
// ----------------------------- ActorID -------------------------------------------- // ----------------------------- ActorID --------------------------------------------
type ActorID struct { type ActorID struct {
ID string ID string
Source string Source string
Schema string HostSchema string
Path string Path string
Host string Host string
Port string HostPort uint16
UnvalidatedInput string UnvalidatedInput string
IsPortSupplemented bool
} }
// Factory function for ActorID. Created struct is asserted to be valid // 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 { func (id ActorID) AsURI() string {
var result 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 { } 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 return result
} }
func (id ActorID) Validate() []string { func (id ActorID) Validate() []string {
var result []string var result []string
result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...) 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.Path, "path")...)
result = append(result, validation.ValidateNotEmpty(id.Host, "host")...) 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")...) result = append(result, validation.ValidateNotEmpty(id.UnvalidatedInput, "unvalidatedInput")...)
if id.UnvalidatedInput != id.AsURI() { if id.UnvalidatedInput != id.AsURI() {
@ -104,12 +109,14 @@ func (id PersonID) Validate() []string {
result := id.ActorID.Validate() result := id.ActorID.Validate()
result = append(result, validation.ValidateNotEmpty(id.Source, "source")...) result = append(result, validation.ValidateNotEmpty(id.Source, "source")...)
result = append(result, validation.ValidateOneOf(id.Source, []any{"forgejo", "gitea"}, "Source")...) result = append(result, validation.ValidateOneOf(id.Source, []any{"forgejo", "gitea"}, "Source")...)
switch id.Source { switch id.Source {
case "forgejo", "gitea": case "forgejo", "gitea":
if strings.ToLower(id.Path) != "api/v1/activitypub/user-id" && strings.ToLower(id.Path) != "api/activitypub/user-id" { 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)) result = append(result, fmt.Sprintf("path: %q has to be a person specific api path", id.Path))
} }
} }
return result return result
} }
@ -168,6 +175,8 @@ func removeEmptyStrings(ls []string) []string {
return rs return rs
} }
// ----------------------------- newActorID --------------------------------------------
func newActorID(uri string) (ActorID, error) { func newActorID(uri string) (ActorID, error) {
validatedURI, err := url.ParseRequestURI(uri) validatedURI, err := url.ParseRequestURI(uri)
if err != nil { if err != nil {
@ -179,15 +188,27 @@ func newActorID(uri string) (ActorID, error) {
} }
length := len(pathWithActorID) length := len(pathWithActorID)
pathWithoutActorID := strings.Join(pathWithActorID[0:length-1], "/") pathWithoutActorID := strings.Join(pathWithActorID[0:length-1], "/")
id := pathWithActorID[length-1] id := strings.ToLower(pathWithActorID[length-1])
result := ActorID{} result := ActorID{}
result.ID = id result.ID = id
result.Schema = validatedURI.Scheme result.HostSchema = strings.ToLower(validatedURI.Scheme)
result.Host = validatedURI.Hostname() result.Host = strings.ToLower(validatedURI.Hostname())
result.Path = pathWithoutActorID result.Path = strings.ToLower(pathWithoutActorID)
result.Port = validatedURI.Port()
result.UnvalidatedInput = uri 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 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 // SPDX-License-Identifier: MIT
package forgefed package forgefed
@ -18,11 +18,13 @@ func TestNewPersonId(t *testing.T) {
expected := PersonID{} expected := PersonID{}
expected.ID = "1" expected.ID = "1"
expected.Source = "forgejo" expected.Source = "forgejo"
expected.Schema = "https" expected.HostSchema = "https"
expected.Path = "api/v1/activitypub/user-id" expected.Path = "api/v1/activitypub/user-id"
expected.Host = "an.other.host" 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" 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") sut, _ := NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected { if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut) t.Errorf("expected: %v\n but was: %v\n", expected, sut)
@ -31,15 +33,47 @@ func TestNewPersonId(t *testing.T) {
expected = PersonID{} expected = PersonID{}
expected.ID = "1" expected.ID = "1"
expected.Source = "forgejo" expected.Source = "forgejo"
expected.Schema = "https" expected.HostSchema = "https"
expected.Path = "api/v1/activitypub/user-id" expected.Path = "api/v1/activitypub/user-id"
expected.Host = "an.other.host" 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" 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") sut, _ = NewPersonID("https://an.other.host:443/api/v1/activitypub/user-id/1", "forgejo")
if sut != expected { if sut != expected {
t.Errorf("expected: %v\n but was: %v\n", expected, sut) 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) { func TestNewRepositoryId(t *testing.T) {
@ -47,10 +81,11 @@ func TestNewRepositoryId(t *testing.T) {
expected := RepositoryID{} expected := RepositoryID{}
expected.ID = "1" expected.ID = "1"
expected.Source = "forgejo" expected.Source = "forgejo"
expected.Schema = "http" expected.HostSchema = "http"
expected.Path = "api/activitypub/repository-id" expected.Path = "api/activitypub/repository-id"
expected.Host = "localhost" expected.Host = "localhost"
expected.Port = "3000" expected.HostPort = 3000
expected.IsPortSupplemented = false
expected.UnvalidatedInput = "http://localhost:3000/api/activitypub/repository-id/1" expected.UnvalidatedInput = "http://localhost:3000/api/activitypub/repository-id/1"
sut, _ := NewRepositoryID("http://localhost:3000/api/activitypub/repository-id/1", "forgejo") sut, _ := NewRepositoryID("http://localhost:3000/api/activitypub/repository-id/1", "forgejo")
if sut != expected { if sut != expected {
@ -61,10 +96,11 @@ func TestNewRepositoryId(t *testing.T) {
func TestActorIdValidation(t *testing.T) { func TestActorIdValidation(t *testing.T) {
sut := ActorID{} sut := ActorID{}
sut.Source = "forgejo" sut.Source = "forgejo"
sut.Schema = "https" sut.HostSchema = "https"
sut.Path = "api/v1/activitypub/user-id" sut.Path = "api/v1/activitypub/user-id"
sut.Host = "an.other.host" sut.Host = "an.other.host"
sut.Port = "" sut.HostPort = 443
sut.IsPortSupplemented = true
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/" sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/"
if sut.Validate()[0] != "userId should not be empty" { if sut.Validate()[0] != "userId should not be empty" {
t.Errorf("validation error expected but was: %v\n", sut.Validate()) t.Errorf("validation error expected but was: %v\n", sut.Validate())
@ -73,10 +109,11 @@ func TestActorIdValidation(t *testing.T) {
sut = ActorID{} sut = ActorID{}
sut.ID = "1" sut.ID = "1"
sut.Source = "forgejo" sut.Source = "forgejo"
sut.Schema = "https" sut.HostSchema = "https"
sut.Path = "api/v1/activitypub/user-id" sut.Path = "api/v1/activitypub/user-id"
sut.Host = "an.other.host" 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" 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\"" { 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]) t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
@ -87,10 +124,11 @@ func TestPersonIdValidation(t *testing.T) {
sut := PersonID{} sut := PersonID{}
sut.ID = "1" sut.ID = "1"
sut.Source = "forgejo" sut.Source = "forgejo"
sut.Schema = "https" sut.HostSchema = "https"
sut.Path = "path" sut.Path = "path"
sut.Host = "an.other.host" sut.Host = "an.other.host"
sut.Port = "" sut.HostPort = 443
sut.IsPortSupplemented = true
sut.UnvalidatedInput = "https://an.other.host/path/1" sut.UnvalidatedInput = "https://an.other.host/path/1"
_, err := validation.IsValid(sut) _, err := validation.IsValid(sut)
@ -101,10 +139,11 @@ func TestPersonIdValidation(t *testing.T) {
sut = PersonID{} sut = PersonID{}
sut.ID = "1" sut.ID = "1"
sut.Source = "forgejox" sut.Source = "forgejox"
sut.Schema = "https" sut.HostSchema = "https"
sut.Path = "api/v1/activitypub/user-id" sut.Path = "api/v1/activitypub/user-id"
sut.Host = "an.other.host" 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" 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]" { 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]) 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) { func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
var err any var err any
// TODO: remove after test _, err = NewPersonID("", "forgejo")
//_, err = NewPersonId("", "forgejo") if err == nil {
//if err == nil { t.Errorf("empty input should be invalid.")
// t.Errorf("empty input should be invalid.") }
//}
_, err = NewPersonID("http://localhost:3000/api/v1/something", "forgejo") _, err = NewPersonID("http://localhost:3000/api/v1/something", "forgejo")
if err == nil { if err == nil {
t.Errorf("localhost uris are not external") t.Errorf("localhost uris are not external")
@ -155,7 +192,6 @@ func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
if err == nil { if err == nil {
t.Errorf("uri may not contain unparsed elements") t.Errorf("uri may not contain unparsed elements")
} }
_, err = NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo") _, err = NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
if err != nil { if err != nil {
t.Errorf("this uri should be valid but was: %v", err) 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 // SPDX-License-Identifier: MIT
package forgefed package forgefed
@ -10,10 +10,10 @@ import (
func (id ActorID) AsWellKnownNodeInfoURI() string { func (id ActorID) AsWellKnownNodeInfoURI() string {
wellKnownPath := ".well-known/nodeinfo" wellKnownPath := ".well-known/nodeinfo"
var result string var result string
if id.Port == "" { if id.HostPort == 0 {
result = fmt.Sprintf("%s://%s/%s", id.Schema, id.Host, wellKnownPath) result = fmt.Sprintf("%s://%s/%s", id.HostSchema, id.Host, wellKnownPath)
} else { } 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 return result
} }

View file

@ -432,6 +432,11 @@ func (c *Commit) GetBranchName() (string, error) {
return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil 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. // CommitFileStatus represents status of files in a commit.
type CommitFileStatus struct { type CommitFileStatus struct {
Added []string Added []string

View file

@ -5,6 +5,7 @@ package git
import ( import (
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"testing" "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) { func Test_parseSubmoduleContent(t *testing.T) {
submoduleFiles := []struct { submoduleFiles := []struct {
fileContent string fileContent string

View file

@ -278,6 +278,49 @@ func syncGitConfig() (err error) {
return err 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 // By default partial clones are disabled, enable them from git v2.22
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
if err = configSet("uploadpack.allowfilter", "true"); err != nil { if err = configSet("uploadpack.allowfilter", "true"); err != nil {
@ -324,6 +367,15 @@ func CheckGitVersionEqual(equal string) error {
return nil 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 { func configSet(key, value string) error {
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
if err != nil && !IsErrorExitCode(err, 1) { if err != nil && !IsErrorExitCode(err, 1) {

View file

@ -11,8 +11,10 @@ import (
"testing" "testing"
"forgejo.org/modules/setting" "forgejo.org/modules/setting"
"forgejo.org/modules/test"
"forgejo.org/modules/util" "forgejo.org/modules/util"
"github.com/hashicorp/go-version"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -94,3 +96,57 @@ func TestSyncConfig(t *testing.T) {
assert.True(t, gitConfigContains("[sync-test]")) assert.True(t, gitConfigContains("[sync-test]"))
assert.True(t, gitConfigContains("cfg-key-a = CfgValA")) 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) { func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
if CheckGitVersionAtLeast("2.7.0") == nil { if CheckGitVersionAtLeast("2.7.0") == nil {
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)"). command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix)
AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commit.ID.String(), BranchPrefix). if limit != -1 {
RunStdString(&RunOpts{Dir: repo.Path}) command = command.AddOptionFormat("--count=%d", limit)
}
stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path})
if err != nil { if err != nil {
return nil, err 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 { if opts.Mode == internal.CodeSearchModeUnion {
query := bleve.NewDisjunctionQuery() query := bleve.NewDisjunctionQuery()
for _, field := range strings.Fields(opts.Keyword) { 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 keywordQuery = query
} else { } else {
keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, 0) keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, false)
} }
if len(opts.RepoIDs) > 0 { if len(opts.RepoIDs) > 0 {

View file

@ -65,5 +65,7 @@ func TokenizerConstructor(config map[string]any, cache *registry.Cache) (analysi
} }
func init() { 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 // 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 := bleve.NewMatchPhraseQuery(matchPhrase)
q.FieldVal = field q.FieldVal = field
q.Analyzer = analyzer q.Analyzer = analyzer
q.Fuzziness = fuzziness q.SetAutoFuzziness(autoFuzzy)
return q return q
} }

View file

@ -162,15 +162,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
} }
q := bleve.NewBooleanQuery() q := bleve.NewBooleanQuery()
for _, token := range tokens { 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( innerQ := bleve.NewDisjunctionQuery(
inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, fuzziness), inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, token.Fuzzy),
inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, fuzziness), inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, token.Fuzzy),
inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, fuzziness)) inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, token.Fuzzy))
switch token.Kind { switch token.Kind {
case internal.BoolOptMust: case internal.BoolOptMust:

View file

@ -22,6 +22,7 @@ import (
type MockRedisClient struct { type MockRedisClient struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockRedisClientMockRecorder recorder *MockRedisClientMockRecorder
isgomock struct{}
} }
// MockRedisClientMockRecorder is the mock recorder for MockRedisClient. // MockRedisClientMockRecorder is the mock recorder for MockRedisClient.
@ -56,38 +57,38 @@ func (mr *MockRedisClientMockRecorder) Close() *gomock.Call {
} }
// DBSize mocks base method. // 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() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DBSize", arg0) ret := m.ctrl.Call(m, "DBSize", ctx)
ret0, _ := ret[0].(*redis.IntCmd) ret0, _ := ret[0].(*redis.IntCmd)
return ret0 return ret0
} }
// DBSize indicates an expected call of DBSize. // 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() 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. // 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() 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) ret0, _ := ret[0].(*redis.IntCmd)
return ret0 return ret0
} }
// Decr indicates an expected call of Decr. // 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() 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. // 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() m.ctrl.T.Helper()
varargs := []any{arg0} varargs := []any{ctx}
for _, a := range arg1 { for _, a := range keys {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "Del", varargs...) 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. // 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() 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...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockRedisClient)(nil).Del), varargs...)
} }
// Exists mocks base method. // 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() m.ctrl.T.Helper()
varargs := []any{arg0} varargs := []any{ctx}
for _, a := range arg1 { for _, a := range keys {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "Exists", varargs...) 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. // 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() 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...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockRedisClient)(nil).Exists), varargs...)
} }
// FlushDB mocks base method. // 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() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FlushDB", arg0) ret := m.ctrl.Call(m, "FlushDB", ctx)
ret0, _ := ret[0].(*redis.StatusCmd) ret0, _ := ret[0].(*redis.StatusCmd)
return ret0 return ret0
} }
// FlushDB indicates an expected call of FlushDB. // 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() 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. // 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() 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) ret0, _ := ret[0].(*redis.StringCmd)
return ret0 return ret0
} }
// Get indicates an expected call of Get. // 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() 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. // 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() m.ctrl.T.Helper()
varargs := []any{arg0, arg1} varargs := []any{ctx, key}
for _, a := range arg2 { for _, a := range fields {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "HDel", varargs...) 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. // 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() 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...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HDel", reflect.TypeOf((*MockRedisClient)(nil).HDel), varargs...)
} }
// HKeys mocks base method. // 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() 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) ret0, _ := ret[0].(*redis.StringSliceCmd)
return ret0 return ret0
} }
// HKeys indicates an expected call of HKeys. // 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() 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. // 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() m.ctrl.T.Helper()
varargs := []any{arg0, arg1} varargs := []any{ctx, key}
for _, a := range arg2 { for _, a := range values {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "HSet", varargs...) 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. // 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() 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...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSet", reflect.TypeOf((*MockRedisClient)(nil).HSet), varargs...)
} }
// Incr mocks base method. // 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() 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) ret0, _ := ret[0].(*redis.IntCmd)
return ret0 return ret0
} }
// Incr indicates an expected call of Incr. // 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() 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. // 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() 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) ret0, _ := ret[0].(*redis.IntCmd)
return ret0 return ret0
} }
// LLen indicates an expected call of LLen. // 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() 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. // 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() 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) ret0, _ := ret[0].(*redis.StringCmd)
return ret0 return ret0
} }
// LPop indicates an expected call of LPop. // 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() 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. // 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() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Ping", arg0) ret := m.ctrl.Call(m, "Ping", ctx)
ret0, _ := ret[0].(*redis.StatusCmd) ret0, _ := ret[0].(*redis.StatusCmd)
return ret0 return ret0
} }
// Ping indicates an expected call of Ping. // 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() 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. // 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() m.ctrl.T.Helper()
varargs := []any{arg0, arg1} varargs := []any{ctx, key}
for _, a := range arg2 { for _, a := range values {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "RPush", varargs...) 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. // 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() 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...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RPush", reflect.TypeOf((*MockRedisClient)(nil).RPush), varargs...)
} }
// SAdd mocks base method. // 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() m.ctrl.T.Helper()
varargs := []any{arg0, arg1} varargs := []any{ctx, key}
for _, a := range arg2 { for _, a := range members {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "SAdd", varargs...) 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. // 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() 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...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SAdd", reflect.TypeOf((*MockRedisClient)(nil).SAdd), varargs...)
} }
// SIsMember mocks base method. // 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() 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) ret0, _ := ret[0].(*redis.BoolCmd)
return ret0 return ret0
} }
// SIsMember indicates an expected call of SIsMember. // 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() 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. // 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() m.ctrl.T.Helper()
varargs := []any{arg0, arg1} varargs := []any{ctx, key}
for _, a := range arg2 { for _, a := range members {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "SRem", varargs...) 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. // 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() 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...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SRem", reflect.TypeOf((*MockRedisClient)(nil).SRem), varargs...)
} }
// Set mocks base method. // 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() 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) ret0, _ := ret[0].(*redis.StatusCmd)
return ret0 return ret0
} }
// Set indicates an expected call of Set. // 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() 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-CN", "简体中文",
"zh-HK", "繁體中文(香港)", "zh-HK", "繁體中文(香港)",
"zh-TW", "繁體中文(台灣)", "zh-TW", "繁體中文(台灣)",
"da", "Danish", "da", "Dansk",
"de-DE", "Deutsch", "de-DE", "Deutsch",
"nds", "Plattdüütsch", "nds", "Plattdüütsch",
"fr-FR", "Français", "fr-FR", "Français",

View file

@ -62,7 +62,7 @@ type MarkupSanitizerRule struct {
func loadMarkupFrom(rootCfg ConfigProvider) { func loadMarkupFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "markdown", &Markdown) 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) FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10) ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10) ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)

View file

@ -4,12 +4,15 @@
package setting package setting
import ( import (
"os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"forgejo.org/modules/log" "forgejo.org/modules/log"
"golang.org/x/crypto/ssh"
) )
// enumerates all the policy repository creating // enumerates all the policy repository creating
@ -26,6 +29,8 @@ var MaxUserCardsPerPage = 36
// MaxForksPerPage sets maximum amount of forks shown per page // MaxForksPerPage sets maximum amount of forks shown per page
var MaxForksPerPage = 40 var MaxForksPerPage = 40
var SSHInstanceKey ssh.PublicKey
// Repository settings // Repository settings
var ( var (
Repository = struct { Repository = struct {
@ -109,6 +114,7 @@ var (
SigningKey string SigningKey string
SigningName string SigningName string
SigningEmail string SigningEmail string
Format string
InitialCommit []string InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"` CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string Merges []string
@ -262,6 +268,7 @@ var (
SigningKey string SigningKey string
SigningName string SigningName string
SigningEmail string SigningEmail string
Format string
InitialCommit []string InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"` CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string Merges []string
@ -271,6 +278,7 @@ var (
SigningKey: "default", SigningKey: "default",
SigningName: "", SigningName: "",
SigningEmail: "", SigningEmail: "",
Format: "openpgp",
InitialCommit: []string{"always"}, InitialCommit: []string{"always"},
CRUDActions: []string{"pubkey", "twofa", "parentsigned"}, CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"}, Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
@ -376,4 +384,15 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
log.Fatal("loadRepoArchiveFrom: %v", err) log.Fatal("loadRepoArchiveFrom: %v", err)
} }
Repository.EnableFlags = sec.Key("ENABLE_FLAGS").MustBool() 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 of the forked repository
Name *string `json:"name"` 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 package structs
import "time"
// FileOptions options for all file APIs // FileOptions options for all file APIs
type FileOptions struct { type FileOptions struct {
// message (optional) for the commit of this file. if not supplied, a default message will be used // 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"` Path string `json:"path"`
SHA string `json:"sha"` SHA string `json:"sha"`
LastCommitSHA string `json:"last_commit_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` will be `file`, `dir`, `symlink`, or `submodule`
Type string `json:"type"` Type string `json:"type"`
Size int64 `json:"size"` Size int64 `json:"size"`

View file

@ -1,4 +1,5 @@
// Copyright 2022 The Gitea Authors. All rights reserved. // Copyright 2022 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package i18n 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 defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok {
if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" { if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" {
format = msg format = msg
found = true
} else if foundIndex { } else if foundIndex {
// Third fallback: old-style default language // Third fallback: old-style default language
if msg, ok := defaultLang.idxToMsgMap[idx]; ok { if msg, ok := defaultLang.idxToMsgMap[idx]; ok {

View file

@ -1,5 +1,4 @@
// Copyright 2024 The Forgejo Authors. All rights reserved. // Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package validation package validation
@ -33,9 +32,9 @@ type Validateable interface {
} }
func IsValid(v Validateable) (bool, error) { func IsValid(v Validateable) (bool, error) {
if err := v.Validate(); len(err) > 0 { if valdationErrors := v.Validate(); len(valdationErrors) > 0 {
typeof := reflect.TypeOf(v) typeof := reflect.TypeOf(v)
errString := strings.Join(err, "\n") errString := strings.Join(valdationErrors, "\n")
return false, ErrNotValid{fmt.Sprint(typeof, ": ", errString)} return false, ErrNotValid{fmt.Sprint(typeof, ": ", errString)}
} }
@ -53,6 +52,10 @@ func ValidateNotEmpty(value any, name string) []string {
if v.IsZero() { if v.IsZero() {
isValid = false isValid = false
} }
case uint16:
if v == 0 {
isValid = false
}
case int64: case int64:
if v == 0 { if v == 0 {
isValid = false 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.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_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_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.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
editor.commit_empty_file_header=Odeslat prázdný soubor 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? 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 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. 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. 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] [graphs]
component_loading_info = Tohle může chvíli trvat… 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_success=Runner byl úspěšně odstraněn
runners.delete_runner_failed=Odstranění runneru selhalo runners.delete_runner_failed=Odstranění runneru selhalo
runners.delete_runner_header=Potvrdit odstranění tohoto runneru 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.unspecified=Neznámý
runners.status.idle=Nečinný runners.status.idle=Nečinný
runners.status.active=Aktivní 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 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. 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. 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] [notification]
watching = Overvåger 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.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_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_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.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_header=Leere Datei committen
editor.commit_empty_file_text=Die Datei, die du commiten willst, ist leer. Fortfahren? 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 migrate.repo_desc_helper = Leer lassen, um vorhandene Beschreibung zu importieren
archive.nocomment = Kommentieren ist nicht möglich, da das Repository archiviert ist. 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. 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] [graphs]
component_loading_failed = Konnte %s nicht laden 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.nocomment = Commenting is not possible because the repository is archived.
archive.pull.noreview = This repository is archived. You cannot review pull requests. 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_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.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories.
form.name_reserved = The repository name "%s" is reserved. 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.cleanuprules.success.delete = Cleanup rule has been deleted.
owner.settings.chef.title = Chef registry owner.settings.chef.title = Chef registry
owner.settings.chef.keypair = Generate key pair 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 = Secrets secrets = Secrets
@ -3871,7 +3875,7 @@ runners.delete_runner = Delete this runner
runners.delete_runner_success = Runner deleted successfully runners.delete_runner_success = Runner deleted successfully
runners.delete_runner_failed = Failed to delete runner runners.delete_runner_failed = Failed to delete runner
runners.delete_runner_header = Confirm to delete this 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.none = No runners available
runners.status.unspecified = Unknown runners.status.unspecified = Unknown
runners.status.idle = Idle runners.status.idle = Idle

View file

@ -38,12 +38,12 @@ passcode=Código de acceso
webauthn_insert_key=Introduzca su clave de seguridad 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_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_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_error=No se pudo leer su llave de seguridad.
webauthn_unsupported_browser=Su navegador no soporta actualmente WebAuthn. 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_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_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_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. webauthn_error_empty=Debe establecer un nombre para esta clave.
@ -72,7 +72,7 @@ all=Todos
sources=Propios sources=Propios
mirrors=Réplica mirrors=Réplica
collaborative=Colaborativo collaborative=Colaborativo
forks=Forks forks=Bifurcaciones
activities=Actividades activities=Actividades
pull_requests=Solicitudes de incorporación de cambios 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=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. 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=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=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. 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. 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_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. 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. 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.` 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. 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. 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. 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=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. openid_desc=OpenID le permite delegar la autenticación a un proveedor externo.
manage_ssh_keys=Gestionar claves SSH manage_ssh_keys=Gestionar claves SSH
@ -1075,6 +1075,14 @@ keep_pronouns_private = Mostrar pronombres solo a personas autenticadas
storage_overview = Resumen del almacenamiento storage_overview = Resumen del almacenamiento
quota.sizes.assets.artifacts = Artefactos quota.sizes.assets.artifacts = Artefactos
quota.sizes.assets.attachments.releases = Archivos adjuntos del lanzamiento 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] [repo]
owner=Propietario 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=Migrado desde <a href="%[1]s">%[2]s</a>
migrated_from_fake=Migrado desde %[1]s migrated_from_fake=Migrado desde %[1]s
migrate.migrate=Migrar desde %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=La migración desde <b>%s</b> ha fallado.
migrate.migrating_failed.error=Error al migrar: %s migrate.migrating_failed.error=Error al migrar: %s
migrate.migrating_failed_no_addr=Migración fallida. 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.open_projects=Proyectos abiertos
issues.new.closed_projects=Proyectos cerrados issues.new.closed_projects=Proyectos cerrados
issues.new.no_items=No hay elementos issues.new.no_items=No hay elementos
issues.new.milestone=Milestone issues.new.milestone=Hito
issues.new.no_milestone=Sin hito issues.new.no_milestone=Sin hito
issues.new.clear_milestone=Limpiar Milestone issues.new.clear_milestone=Limpiar Milestone
issues.new.open_milestone=Hitos abiertos 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.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.remove_ref_at=`eliminó la referencia <b>%s</b> %s`
issues.add_ref_at=`añadió 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=Etiqueta
issues.filter_label_exclude=`Usa <code>alt</code> + <code>clic/enter</code> para excluir etiquetas` 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_no_select=Todas las etiquetas
issues.filter_label_select_no_label=Sin etiqueta 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_all=Todos los hitos
issues.filter_milestone_none=Sin hitos issues.filter_milestone_none=Sin hitos
issues.filter_milestone_open=Abrir 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.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.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 pull.deleted_branch=(eliminado):%s
@ -1976,7 +1984,7 @@ milestones.no_due_date=Sin fecha límite
milestones.open=Abrir milestones.open=Abrir
milestones.close=Cerrar milestones.close=Cerrar
milestones.new_subheader=Los hitos pueden ayudarle a organizar los problemas y monitorizar su progreso. 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.create=Crear hito
milestones.title=Título milestones.title=Título
milestones.desc=Descripción milestones.desc=Descripción
@ -2017,7 +2025,7 @@ ext_wiki=Wiki externa
ext_wiki.desc=Enlace a una wiki externa. ext_wiki.desc=Enlace a una wiki externa.
wiki=Wiki 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.welcome_desc=Esta wiki le permite escribir y compartir documentación con otros colaboradores.
wiki.desc=Escriba y comparta documentación con colaboradores. wiki.desc=Escriba y comparta documentación con colaboradores.
wiki.create_first_page=Crear la primera página 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_create_desc=Rama o etiqueta creada.
settings.event_delete=Eliminar settings.event_delete=Eliminar
settings.event_delete_desc=Rama o etiqueta eliminada. 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_fork_desc=Repositorio forkeado.
settings.event_wiki=Wiki settings.event_wiki=Wiki
settings.event_wiki_desc=Página de la Wiki creada, renombrada, editada o eliminada. 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.archive.tagsettings_unavailable=Los ajustes de las etiquetas no están disponibles si el repositorio está archivado.
settings.unarchive.button=Desarchivar repositorio settings.unarchive.button=Desarchivar repositorio
settings.unarchive.header=Desarchivar este 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.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.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. 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_invalid_lock_directory=No se puede bloquear el directorio: %s
settings.lfs_lock_already_exists=El bloqueo ya existe: %s settings.lfs_lock_already_exists=El bloqueo ya existe: %s
settings.lfs_lock=Bloquear 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_locks_no_locks=Sin bloqueos
settings.lfs_lock_file_no_exist=El archivo bloqueado no existe en la rama por defecto settings.lfs_lock_file_no_exist=El archivo bloqueado no existe en la rama por defecto
settings.lfs_force_unlock=Forzar desbloqueo settings.lfs_force_unlock=Forzar desbloqueo
@ -2637,7 +2645,7 @@ release.cancel=Cancelar
release.publish=Publicar lanzamiento release.publish=Publicar lanzamiento
release.save_draft=Guardar borrador release.save_draft=Guardar borrador
release.edit_release=Actualizar Lanzamiento release.edit_release=Actualizar Lanzamiento
release.delete_release=Eliminar Lanzamiento release.delete_release=Eliminar lanzamiento
release.delete_tag=Eliminar tag release.delete_tag=Eliminar tag
release.deletion=Eliminar lanzamiento 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? 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. settings.enforce_on_admins_desc = Los administradores del repositorio no pueden saltarse esta regla.
pulls.editable = Editable pulls.editable = Editable
issues.filter_no_results = No hay resultados 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] [graphs]
component_loading = Cargando %s... component_loading = Cargando %s
component_loading_failed = No se pudo cargar %s component_loading_failed = No se pudo cargar %s
contributors.what = contribuciones contributors.what = contribuciones
recent_commits.what = commits recientes 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. 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=Público
members.public_helper=hacer oculto members.public_helper=Hacer oculto
members.private=Oculto members.private=Oculto
members.private_helper=hacer público members.private_helper=Hacer público
members.member_role=Rol del miembro: members.member_role=Rol del miembro:
members.owner=Propietario members.owner=Propietario
members.member=Miembro members.member=Miembro
@ -2942,7 +2956,7 @@ members.remove.detail=¿Destituir a %[1]s de %[2]s?
members.leave=Abandonar members.leave=Abandonar
members.leave.detail=¿Irse de %s? members.leave.detail=¿Irse de %s?
members.invite_desc=Añadir un miembro nuevo a %s: members.invite_desc=Añadir un miembro nuevo a %s:
members.invite_now=Invitar members.invite_now=Invitar ahora
teams.join=Unirse teams.join=Unirse
teams.leave=Abandonar 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.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=Sin acceso
teams.none_access_helper=Los miembros no pueden ver o hacer ninguna otra acción en esta unidad. 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.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=Leer
teams.read_access_helper=Los miembros pueden ver y clonar los repositorios del equipo. 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. 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. follow_blocked_user = No puedes seguir a esta organización porque esta organización te ha bloqueado.
open_dashboard = Abrir panel de control 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. 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] [admin]
@ -3539,6 +3553,9 @@ emails.delete_primary_email_error = No puedes eliminar el correo electrónico pr
config.cache_test =Caché de prueba config.cache_test =Caché de prueba
emails.delete_desc = ¿Estás seguro que quieres eliminar esta dirección de correo electrónico? emails.delete_desc = ¿Estás seguro que quieres eliminar esta dirección de correo electrónico?
monitor.duration = Duración (es) 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] [action]
@ -3799,6 +3816,12 @@ alt.install = Instalar paquete
alt.repository = Información del repositorio alt.repository = Información del repositorio
alt.repository.architectures = Arquitecturas alt.repository.architectures = Arquitecturas
alt.repository.multiple_groups = Este paquete está disponible en múltiples grupos. 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]
secrets=Secretos 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.empty_commit_message = (mensaje de commit vacío)
runs.expire_log_message = Los registros han sido eliminados porque eran demasiado antiguos. runs.expire_log_message = Los registros han sido eliminados porque eran demasiado antiguos.
runs.workflow = Flujo de trabajo runs.workflow = Flujo de trabajo
workflow.dispatch.run = Correr flujo de trabajo
workflow.dispatch.use_from = Usar el flujo de trabajo de
[projects] [projects]
type-1.display_name=Proyecto individual 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 exact_tooltip = Incluir sólo los resultados que corresponden al término de búsqueda exacto
issue_kind = Buscar incidencias… issue_kind = Buscar incidencias…
fuzzy = Difusa 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_tooltip = Interpretar los términos de búsqueda como una expresión regular
regexp = 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! 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ä: 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_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. 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_reserved = Käyttäjätunnus "%s" on varattu.
form.name_pattern_not_allowed = Kaava "%s" ei ole sallittu käyttäjätunnuksessa. 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] [settings]
@ -994,12 +997,12 @@ download_tar=Lataa TAR.GZ
repo_desc=Kuvaus repo_desc=Kuvaus
repo_lang=Kieli repo_lang=Kieli
repo_gitignore_helper=Valitse .gitignore-mallit repo_gitignore_helper=Valitse .gitignore-mallit
issue_labels=Ongelmien tunnisteet issue_labels=Tunnisteet
issue_labels_helper=Valitse nimiöjoukko issue_labels_helper=Valitse nimiöjoukko
license=Lisenssi license=Lisenssi
license_helper=Valitse lisenssitiedosto license_helper=Valitse lisenssitiedosto
readme=README readme=README
auto_init=Alusta repo (Luo .gitignore, License ja README) auto_init=Alusta repo
create_repo=Luo repo create_repo=Luo repo
default_branch=Oletushaara default_branch=Oletushaara
mirror_prune=Karsi mirror_prune=Karsi
@ -1168,7 +1171,7 @@ issues.new_label=Uusi tunniste
issues.new_label_placeholder=Tunnisteen nimi issues.new_label_placeholder=Tunnisteen nimi
issues.new_label_desc_placeholder=Kuvaus issues.new_label_desc_placeholder=Kuvaus
issues.create_label=Luo tunniste 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.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.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` 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_open_issues=%d avointa ongelmaa
issues.label_edit=Muokkaa issues.label_edit=Muokkaa
issues.label_delete=Poista issues.label_delete=Poista
issues.label_modify=Muokkaa tunniste issues.label_modify=Muokkaa tunnistetta
issues.label_deletion=Poista tunniste issues.label_deletion=Poista tunniste
issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä
issues.label.filter_sort.reverse_alphabetically=Käänteisessä 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.tracker_auto_close=Ajan seuranta pysähtyy automaattisesti kun tämä ongelma on suljettu
issues.stop_tracking=Pysäytä ajanotto issues.stop_tracking=Pysäytä ajanotto
issues.stop_tracking_history=`lopetti työskentelyn %s` 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_short=Lisää aika
issues.add_time_cancel=Peruuta issues.add_time_cancel=Peruuta
issues.add_time_history=`lisäsi käytetyn ajan %s` 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 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. transfer.no_permission_to_accept = Sinulla ei ole oikeutta hyväksyä tätä siirtoa.
settings.web_hook_name_feishu = Feishu / Lark Suite 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] [graphs]
component_loading_info = Tämä saattaa kestää hetken… component_loading_info = Tämä saattaa kestää hetken…
component_failed_to_load = Odottamaton virhe. component_failed_to_load = Odottamaton virhe.
component_loading = Ladataan %s... component_loading = Ladataan %s
contributors.what = kontribuutiot contributors.what = kontribuutiot
recent_commits.what = viimeisimmät kommitit recent_commits.what = viimeisimmät kommitit
code_frequency.what = koodifrekvenssi 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.update_profile_success=Käyttäjän tili on päivitetty.
users.edit_account=Muokkaa käyttäjätiliä users.edit_account=Muokkaa käyttäjätiliä
users.max_repo_creation_desc=(Aseta -1 käyttääksesi globaalia oletusrajaa.) 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.prohibit_login=Ota sisäänkirjautuminen pois käytöstä
users.is_admin=Ylläpitäjätili users.is_admin=Ylläpitäjätili
users.is_restricted=Rajoitettu 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_success=Todennuslähde on päivitetty.
auths.update=Päivitä todennuslähde auths.update=Päivitä todennuslähde
auths.delete=Poista 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.delete_auth_desc=Todennuslähteen poisto estää käyttäjiä käyttämästä sitä kirjautumiseen. Jatketaanko?
auths.deletion_success=Todennuslähde on poistettu. 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.enable_captcha=Ota CAPTCHA käyttöön
config.active_code_lives=Aktivointikoodin vanhenemisaika config.active_code_lives=Aktivointikoodin vanhenemisaika
config.default_keep_email_private=Piilota sähköpostiosoitteet oletuksena 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.webhook_config=Webkoukkujen asetukset
config.queue_length=Jonon pituus config.queue_length=Jonon pituus
@ -2468,7 +2520,7 @@ config.cache_conn=Välimuistin yhteys merkkijono
config.session_config=Istunnon asetukset config.session_config=Istunnon asetukset
config.session_provider=Istunnon toimittaja config.session_provider=Istunnon toimittaja
config.provider_config=Toimittajan asetukset 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.gc_interval_time=GC aikaväli aika
config.session_life_time=Istunnon elinikä config.session_life_time=Istunnon elinikä
config.https_only=Vain HTTPS 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.download_diagnosis_report = Lataa diagnostiikkaraportti
monitor.duration = Kesto (s) monitor.duration = Kesto (s)
monitor.last_execution_result = Tulos 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] [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 commitstatus.pending = Nakabinbin
issues.review.pending = Nakabinbin issues.review.pending = Nakabinbin
pulls.status_checking = Nakabinbin ang ilang mga pagsusuri 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. editor.file_already_exists = Umiiral na ang file na may pangalang "%s" sa repositoryong ito.
issues.review.review = Suriin issues.review.review = Suriin
activity.git_stats_push_to_branch = sa %s at 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 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. 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. 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] [search]
commit_kind = Maghanap ng mga commit… 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 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.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. 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] [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>. 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=Migré de <a href="%[1]s">%[2]s</a>
migrated_from_fake=Migré de %[1]s migrated_from_fake=Migré de %[1]s
migrate.migrate=Migrer depuis %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=La migration de <b>%s</b> a échoué.
migrate.migrating_failed.error=Échec de la migration : %s migrate.migrating_failed.error=Échec de la migration : %s
migrate.migrating_failed_no_addr=Échec de la migration. 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.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_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_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.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_header=Réviser un fichier vide
editor.commit_empty_file_text=Le fichier que vous allez réviser est vide. Continuer ? 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_invalid_lock_directory=Impossible de verrouiller le répertoire : %s
settings.lfs_lock_already_exists=Verrou déjà existant : %s settings.lfs_lock_already_exists=Verrou déjà existant : %s
settings.lfs_lock=Verrou 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_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_lock_file_no_exist=Le fichier verrouillé n'existe pas dans la branche par défaut
settings.lfs_force_unlock=Forcer le déverrouillage 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.browse_further = Continuer la navigation
commits.renamed_from = Renommé depuis %s commits.renamed_from = Renommé depuis %s
pulls.nothing_to_compare_have_tag = La branche ou le tag sélectionné sont identiques. 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. 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 wiki.cancel = Annuler
settings.wiki_globally_editable = Permettre l'édition du wiki a tout le monde 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. 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 editor.commit_email = Courriel de commit
commits.view_single_diff = Voir les changements dans ce fichier introduit par ce 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] [graphs]
component_loading = Chargement %s... component_loading = Chargement %s
component_loading_failed = Échec de chargement de %s component_loading_failed = Échec de chargement de %s
component_loading_info = Cela peut prendre du temps… 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. 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é. follow_blocked_user = Vous ne pouvez pas suivre cette organisation car elle vous a bloqué.
open_dashboard = Ouvrir le tableau de bord 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.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 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.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] [admin]
dashboard=Tableau de bord 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.registry.install = Pour installer le paquet, exécutez la commande suivante :
alt.install = Installer le paquet alt.install = Installer le paquet
alt.repository.multiple_groups = Ce paquet est disponible dans plusieurs groupes. 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=Secrets secrets=Secrets
@ -4005,7 +4042,7 @@ exact_tooltip = Inclure uniquement les résultats qui correspondent exactement a
issue_kind = Rechercher dans les tickets… issue_kind = Rechercher dans les tickets…
union = Union union = Union
union_tooltip = Inclus les résultats contenant au moins un des mots clé séparés par des espaces 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... milestone_kind = Recherche dans les jalons...
regexp_tooltip = Interpréter le terme de recherche comme une expression régulière regexp_tooltip = Interpréter le terme de recherche comme une expression régulière
regexp = RegExp 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. pulls.read = <b>Lire :</b> Lire et créer des demandes de tirage.
[translation_meta] [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

@ -290,4 +290,9 @@ db_type = Tipo de base de datos
app_slogan = Slogan da instancia app_slogan = Slogan da instancia
app_slogan_helper = Escribe o slogan da túa instancia aqui. Ou deixao baleiro para desabilitala. app_slogan_helper = Escribe o slogan da túa instancia aqui. Ou deixao baleiro para desabilitala.
domain = Dominio do servidor domain = Dominio do servidor
ssh_port = Porto do servidor SSH 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 app_desc=Pašmitināms Git pakalpojums bez galvassāpēm
install=Viegli uzstādīt 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>. 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=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! 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 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.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_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_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.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_header=Iesūtīt tukšu datni
editor.commit_empty_file_text=Iesūtāmā datne ir tukša. Turpināt? 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 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. 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. 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] [graphs]
component_loading=Ielādē %s… 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 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. 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. 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] [repo.permissions]
code.read = <b>Lesen:</b> De Quelltext vun deesem Repositorium ankieken un klonen. 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 migrate.repo_desc_helper = Leeg laten om bestaande beschrijving te importeren
archive.nocomment = Commentaar geven is niet mogelijk omdat de repository gearchiveerd is. 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. 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.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_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_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.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_header=Fazer commit de um arquivo vazio
editor.commit_empty_file_text=O arquivo que você está prestes fazer commit está vazio. Continuar? 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. 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 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. 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] [graphs]
component_loading = Carregando %s… 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.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_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_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.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_header=Cometer um ficheiro vazio
editor.commit_empty_file_text=O ficheiro que está prestes a cometer está vazio. Quer continuar? 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 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. 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. 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] [graphs]
component_loading=A carregar %s… component_loading=A carregar %s…

View file

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

View file

@ -231,7 +231,7 @@ platform_desc=Forgejo підтверджено працює на вільних
lightweight=Невибагливість lightweight=Невибагливість
lightweight_desc=Forgejo має низькі вимоги до ресурсів та може працювати на недорогому Raspberry Pi. Заощадьте енергію свого комп'ютера! lightweight_desc=Forgejo має низькі вимоги до ресурсів та може працювати на недорогому Raspberry Pi. Заощадьте енергію свого комп'ютера!
license=Відкритий вихідний код 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_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] [install]
@ -299,7 +299,7 @@ disable_gravatar.description=Вимкнути Gravatar або інші стор
federated_avatar_lookup=Увімкнути федеровані аватари federated_avatar_lookup=Увімкнути федеровані аватари
federated_avatar_lookup.description=Увімкнути зовнішні аватари за допомогою Libravatar. federated_avatar_lookup.description=Увімкнути зовнішні аватари за допомогою Libravatar.
disable_registration=Вимкнути самостійну реєстрацію disable_registration=Вимкнути самостійну реєстрацію
disable_registration.description=Тільки адміністратор може створювати нові облікові записи. Наполегливо рекомендуємо залишити реєстрацію вимкненою, якщо ви не збираєтеся розміщувати загальнодоступний екземпляр та сприяти появі величезної кількості спам-акаунтів. disable_registration.description=Тільки адміністратор може створювати нові облікові записи. Настійно рекомендуємо залишити реєстрацію вимкненою, якщо ви не збираєтеся розміщувати загальнодоступний екземпляр та сприяти появі величезної кількості спам-акаунтів.
allow_only_external_registration.description=Користувачам буде дозволено реєструватись лише через налаштовані сторонні сервіси. allow_only_external_registration.description=Користувачам буде дозволено реєструватись лише через налаштовані сторонні сервіси.
openid_signin=Увімкнути реєстрацію за допомогою OpenID openid_signin=Увімкнути реєстрацію за допомогою OpenID
openid_signin.description=Увімкнути вхід за допомогою OpenID. openid_signin.description=Увімкнути вхід за допомогою OpenID.
@ -2659,6 +2659,9 @@ release.asset_name = Назва ресурсу
release.add_external_asset = Додати зовнішній ресурс release.add_external_asset = Додати зовнішній ресурс
find_file.no_matching = Не знайдено відповідного файлу find_file.no_matching = Не знайдено відповідного файлу
commits.search.tooltip = До ключових слів можна додавати префікси «author:», «committer:», «after:» або «before:», наприклад, «revert author:Alice before:2019-01-13». 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] [graphs]
contributors.what = внески contributors.what = внески

View file

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

View file

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

View file

@ -1,21 +1,4 @@
# Forgejo translations See [locale_readme.md](../locale_readme.md) for modification instructions.
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/
## Attribution ## Attribution

View file

@ -20,5 +20,8 @@
"themes.names.forgejo-light": "Forgejo světlé", "themes.names.forgejo-light": "Forgejo světlé",
"themes.names.forgejo-dark": "Forgejo tmavé", "themes.names.forgejo-dark": "Forgejo tmavé",
"error.not_found.title": "Stránka nenalezena", "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", "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.", "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.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", "alert.range_error": " skal være et tal mellem %[1]s og %[2]s.",
"settings.adopt": "Adoptere", "meta.last_line": "Livet er det dejligste eventyr. - (H.C. Andersen)"
"alert.range_error": " skal være et tal mellem %[1]s og %[2]s."
} }

View file

@ -18,5 +18,8 @@
"themes.names.forgejo-light": "Forgejo hell", "themes.names.forgejo-light": "Forgejo hell",
"themes.names.forgejo-dark": "Forgejo dunkel", "themes.names.forgejo-dark": "Forgejo dunkel",
"error.not_found.title": "Seite nicht gefunden", "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.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>", "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", "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.", "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)", "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.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_repos": "Tutki repositorioita",
"home.explore_users": "Tutki käyttäjiä", "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_repos": "Tuklasin ang mga repositoryo",
"home.explore_users": "Tuklasin ang mga user", "home.explore_users": "Tuklasin ang mga user",
"home.explore_orgs": "Tuklasin ang mga organisasyon", "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-light": "Forgejo gaišais",
"themes.names.forgejo-dark": "Forgejo tumšais", "themes.names.forgejo-dark": "Forgejo tumšais",
"error.not_found.title": "Lapa nav atrasta", "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)", "themes.names.forgejo-auto": "Forgejo (Systeem-Thema nagahn)",
"error.not_found.title": "Sied nich funnen", "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.", "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", "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", "themes.names.forgejo-dark": "Forgejo donker",
"error.not_found.title": "Pagina niet gevonden", "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.", "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.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-light": "Forgejo claro",
"themes.names.forgejo-dark": "Forgejo escuro", "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": "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-auto": "Forgejo (segue o tema do sistema)",
"themes.names.forgejo-light": "Forgejo claro", "themes.names.forgejo-light": "Forgejo claro",
"themes.names.forgejo-dark": "Forgejo escuro", "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-light": "Forgejo светлая",
"themes.names.forgejo-auto": "Forgejo как в системе", "themes.names.forgejo-auto": "Forgejo как в системе",
"themes.names.forgejo-dark": "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": "Сторінку не знайдено", "error.not_found.title": "Сторінку не знайдено",
"alert.asset_load_failed": "Не вдалося завантажити файли ресурсів з {path}. Переконайтеся, що до файлів ресурсів є доступ.", "alert.asset_load_failed": "Не вдалося завантажити файли ресурсів з {path}. Переконайтеся, що до файлів ресурсів є доступ.",
"install.invalid_lfs_path": "Не вдалося створити корінь LFS за вказаним шляхом: %[1]s", "install.invalid_lfs_path": "Не вдалося створити корінь LFS за вказаним шляхом: %[1]s",
"settings.adopt": "Прийняти", "alert.range_error": " має бути числом від %[1]s до %[2]s.",
"install.lfs_jwt_secret_failed": "Не вдалося створити секрет LFS JWT: %[1]s", "meta.last_line": "Не зливай злий запити на злиття — зіллється зле."
"alert.range_error": " має бути числом від %[1]s до %[2]s."
} }

View file

@ -12,8 +12,8 @@
"themes.names.forgejo-light": "Forgejo 浅色", "themes.names.forgejo-light": "Forgejo 浅色",
"themes.names.forgejo-dark": "Forgejo 深色", "themes.names.forgejo-dark": "Forgejo 深色",
"error.not_found.title": "页面不存在", "error.not_found.title": "页面不存在",
"alert.asset_load_failed": "无法从 {path} 加载资源文件。请确保可以访问资源文件。", "alert.asset_load_failed": "无法从 {path} 加载资源文件。请确保资源文件可被访问。",
"install.lfs_jwt_secret_failed": "无法生成 LFS JWT 密钥:%[1]s",
"install.invalid_lfs_path": "无法在指定路径创建 LFS 根目录:%[1]s", "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", "@primer/octicons": "19.14.0",
"ansi_up": "6.0.5", "ansi_up": "6.0.5",
"asciinema-player": "3.8.2", "asciinema-player": "3.8.2",
"chart.js": "4.4.5", "chart.js": "4.4.9",
"chartjs-adapter-dayjs-4": "1.0.4", "chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0", "chartjs-plugin-zoom": "2.2.0",
"clippie": "4.1.1", "clippie": "4.1.6",
"css-loader": "7.0.0", "css-loader": "7.0.0",
"dayjs": "1.11.12", "dayjs": "1.11.12",
"dropzone": "6.0.0-beta.2", "dropzone": "6.0.0-beta.2",
@ -30,7 +30,7 @@
"htmx.org": "1.9.12", "htmx.org": "1.9.12",
"idiomorph": "0.3.0", "idiomorph": "0.3.0",
"jquery": "3.7.1", "jquery": "3.7.1",
"katex": "0.16.21", "katex": "0.16.22",
"mermaid": "11.6.0", "mermaid": "11.6.0",
"mini-css-extract-plugin": "2.9.2", "mini-css-extract-plugin": "2.9.2",
"minimatch": "10.0.1", "minimatch": "10.0.1",
@ -55,7 +55,7 @@
"vue-chartjs": "5.3.1", "vue-chartjs": "5.3.1",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5", "vue3-calendar-heatmap": "2.0.5",
"webpack": "5.98.0", "webpack": "5.99.6",
"webpack-cli": "6.0.1", "webpack-cli": "6.0.1",
"wrap-ansi": "9.0.0" "wrap-ansi": "9.0.0"
}, },
@ -91,6 +91,7 @@
"license-checker-rseidelsohn": "4.4.2", "license-checker-rseidelsohn": "4.4.2",
"markdownlint-cli": "0.44.0", "markdownlint-cli": "0.44.0",
"postcss-html": "1.8.0", "postcss-html": "1.8.0",
"sharp": "0.34.1",
"stylelint": "16.17.0", "stylelint": "16.17.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.11", "stylelint-declaration-strict-value": "1.10.11",

14
poetry.lock generated
View file

@ -130,17 +130,17 @@ six = ">=1.13.0"
[[package]] [[package]]
name = "json5" name = "json5"
version = "0.10.0" version = "0.12.0"
description = "A Python implementation of the JSON5 data format." description = "A Python implementation of the JSON5 data format."
optional = false optional = false
python-versions = ">=3.8.0" python-versions = ">=3.8.0"
files = [ files = [
{file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"}, {file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"},
{file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"}, {file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"},
] ]
[package.extras] [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]] [[package]]
name = "pathspec" name = "pathspec"
@ -393,13 +393,13 @@ telegram = ["requests"]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.13.0" version = "4.13.2"
description = "Backported and Experimental Type Hints for Python 3.8+" description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"}, {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
{file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
] ]
[[package]] [[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