mirror of
https://codeberg.org/davrot/forgejo.git
synced 2025-06-05 18:00:02 +02:00
Merge branch 'forgejo' into upload_with_path_structure
Some checks are pending
Integration tests for the release process / release-simulation (push) Waiting to run
Some checks are pending
Integration tests for the release process / release-simulation (push) Waiting to run
This commit is contained in:
commit
9dfa38225e
199 changed files with 4771 additions and 1806 deletions
|
@ -4,12 +4,12 @@
|
|||
"features": {
|
||||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20"
|
||||
"version": "22"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.2.3": {},
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"version": "3.12"
|
||||
"version": "3.13"
|
||||
},
|
||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
||||
},
|
||||
|
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.4
|
||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.5
|
||||
with:
|
||||
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||
strategy: ort
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: data.forgejo.org/renovate/renovate:39.222.1
|
||||
image: data.forgejo.org/renovate/renovate:39.252.0
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
|
|
11
Makefile
11
Makefile
|
@ -39,17 +39,17 @@ XGO_VERSION := go-1.21.x
|
|||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.2 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.31.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.32.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.1 # renovate: datasource=go
|
||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@39.222.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@39.252.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
|
||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||
|
@ -1017,8 +1017,7 @@ generate-gomock:
|
|||
|
||||
.PHONY: generate-images
|
||||
generate-images: | node_modules
|
||||
npm install --no-save fabric@6 imagemin-zopfli@7
|
||||
node tools/generate-images.js $(TAGS)
|
||||
node tools/generate-images.js
|
||||
|
||||
.PHONY: generate-manpage
|
||||
generate-manpage:
|
||||
|
|
4
assets/go-licenses.json
generated
4
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,7 @@ package cmd
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
|
@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
|
|||
Name: "access-token",
|
||||
Usage: "Generate access token for the user",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "access-token-name",
|
||||
Usage: `Name of the generated access token`,
|
||||
Value: "gitea-admin",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "access-token-scopes",
|
||||
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||
Value: "all",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "restricted",
|
||||
Usage: "Make a restricted user account",
|
||||
|
@ -157,23 +168,40 @@ func runCreateUser(c *cli.Context) error {
|
|||
IsRestricted: restricted,
|
||||
}
|
||||
|
||||
var accessTokenName string
|
||||
var accessTokenScope auth_model.AccessTokenScope
|
||||
if c.IsSet("access-token") {
|
||||
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
|
||||
if accessTokenName == "" {
|
||||
return errors.New("access-token-name cannot be empty")
|
||||
}
|
||||
var err error
|
||||
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||
}
|
||||
if !accessTokenScope.HasPermissionScope() {
|
||||
return errors.New("access token does not have any permission")
|
||||
}
|
||||
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
|
||||
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||
}
|
||||
|
||||
// arguments should be prepared before creating the user & access token, in case there is anything wrong
|
||||
|
||||
// create the user
|
||||
if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
|
||||
return fmt.Errorf("CreateUser: %w", err)
|
||||
}
|
||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||
|
||||
if c.Bool("access-token") {
|
||||
t := &auth_model.AccessToken{
|
||||
Name: "gitea-admin",
|
||||
UID: u.ID,
|
||||
}
|
||||
|
||||
// create the access token
|
||||
if accessTokenScope != "" {
|
||||
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
|
||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||
}
|
||||
|
||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
|||
},
|
||||
&cli.StringFlag{
|
||||
Name: "scopes",
|
||||
Value: "",
|
||||
Usage: "Comma separated list of scopes to apply to access token",
|
||||
Value: "all",
|
||||
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||
},
|
||||
},
|
||||
Action: runGenerateAccessToken,
|
||||
|
@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
|||
|
||||
func runGenerateAccessToken(c *cli.Context) error {
|
||||
if !c.IsSet("username") {
|
||||
return errors.New("You must provide a username to generate a token for")
|
||||
return errors.New("you must provide a username to generate a token for")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals()
|
||||
|
@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||
}
|
||||
if !accessTokenScope.HasPermissionScope() {
|
||||
return errors.New("access token does not have any permission")
|
||||
}
|
||||
t.Scope = accessTokenScope
|
||||
|
||||
// create the token
|
||||
|
|
|
@ -54,8 +54,8 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||
altTLSALPNPort = p
|
||||
}
|
||||
|
||||
magic := certmagic.NewDefault()
|
||||
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||
|
||||
// Try to use private CA root if provided, otherwise defaults to system's trust
|
||||
var certPool *x509.CertPool
|
||||
if setting.AcmeCARoot != "" {
|
||||
|
@ -65,7 +65,8 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
|
||||
}
|
||||
}
|
||||
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
|
||||
|
||||
certmagic.DefaultACME = certmagic.ACMEIssuer{
|
||||
CA: setting.AcmeURL,
|
||||
TrustedRoots: certPool,
|
||||
Email: setting.AcmeEmail,
|
||||
|
@ -75,7 +76,11 @@ func runACME(listenAddr string, m http.Handler) error {
|
|||
ListenHost: setting.HTTPAddr,
|
||||
AltTLSALPNPort: altTLSALPNPort,
|
||||
AltHTTPPort: altHTTPPort,
|
||||
})
|
||||
}
|
||||
|
||||
magic := certmagic.NewDefault()
|
||||
|
||||
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
|
||||
|
||||
magic.Issuers = []certmagic.Issuer{myACME}
|
||||
|
||||
|
|
|
@ -1163,9 +1163,13 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; Signing format that Forgejo should use, openpgp uses GPG and ssh uses OpenSSH.
|
||||
;FORMAT = openpgp
|
||||
;;
|
||||
;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
|
||||
;; run in the context of the RUN_USER
|
||||
;; Switch to none to stop signing completely
|
||||
;; Switch to none to stop signing completely.
|
||||
;; If `FORMAT` is set to **ssh** this should be set to an absolute path to an public OpenSSH key.
|
||||
;SIGNING_KEY = default
|
||||
;;
|
||||
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
|
||||
|
@ -2408,7 +2412,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; The first locale will be used as the default if user browser's language doesn't match any locale in the list.
|
||||
;LANGS = en-US,zh-CN,zh-HK,zh-TW,da,de-DE,nds,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg,it-IT,fi-FI,fil,eo,tr-TR,cs-CZ,sl,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID
|
||||
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Danish,Deutsch,Plattdüütsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Filipino,Esperanto,Türkçe,Čeština,Slovenščina,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia
|
||||
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Dansk,Deutsch,Plattdüütsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Filipino,Esperanto,Türkçe,Čeština,Slovenščina,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2440,7 +2444,7 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
||||
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
|
||||
;MERMAID_MAX_SOURCE_CHARACTERS = 50000
|
||||
;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
|
||||
;FILEPREVIEW_MAX_LINES = 50
|
||||
|
||||
|
|
|
@ -31,6 +31,21 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
|
|||
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
|
||||
fi
|
||||
|
||||
# In case someone wants to sign the `{keyname}.pub` key by `ssh-keygen -s ca -I identity ...` to
|
||||
# make use of the ssh-key certificate authority feature (see ssh-keygen CERTIFICATES section),
|
||||
# the generated key file name is `{keyname}-cert.pub`
|
||||
if [ -e /data/ssh/ssh_host_ed25519_key-cert.pub ]; then
|
||||
SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_key-cert.pub"}
|
||||
fi
|
||||
|
||||
if [ -e /data/ssh/ssh_host_rsa_key-cert.pub ]; then
|
||||
SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_key-cert.pub"}
|
||||
fi
|
||||
|
||||
if [ -e /data/ssh/ssh_host_ecdsa_key-cert.pub ]; then
|
||||
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"}
|
||||
fi
|
||||
|
||||
if [ -d /etc/ssh ]; then
|
||||
SSH_PORT=${SSH_PORT:-"22"} \
|
||||
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \
|
||||
|
|
58
go.mod
58
go.mod
|
@ -25,9 +25,9 @@ require (
|
|||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2
|
||||
github.com/alecthomas/chroma/v2 v2.16.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.4.4
|
||||
github.com/blevesearch/bleve/v2 v2.5.0
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
||||
github.com/caddyserver/certmagic v0.22.2
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/djherbis/buffer v1.2.0
|
||||
github.com/djherbis/nio/v3 v3.0.1
|
||||
|
@ -36,7 +36,7 @@ require (
|
|||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
github.com/felixge/fgprof v0.9.5
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
|
@ -67,16 +67,16 @@ require (
|
|||
github.com/jhillyerd/enmime/v2 v2.1.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.17.11
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.10
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/markbates/goth v1.80.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.27
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/meilisearch/meilisearch-go v0.31.0
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/minio-go/v7 v7.0.88
|
||||
github.com/minio/minio-go/v7 v7.0.90
|
||||
github.com/msteinert/pam/v2 v2.0.0
|
||||
github.com/nektos/act v0.2.52
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
|
@ -100,14 +100,14 @@ require (
|
|||
github.com/yuin/goldmark v1.7.8
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go v0.126.0
|
||||
go.uber.org/mock v0.5.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/image v0.25.0
|
||||
golang.org/x/net v0.38.0
|
||||
golang.org/x/oauth2 v0.28.0
|
||||
golang.org/x/sync v0.12.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
go.uber.org/mock v0.5.1
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/image v0.26.0
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/oauth2 v0.29.0
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/sys v0.32.0
|
||||
golang.org/x/text v0.24.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
@ -134,30 +134,30 @@ require (
|
|||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.3 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.1.12 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.7 // indirect
|
||||
github.com/blevesearch/geo v0.1.20 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.24 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.25 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.0.10 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.16 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b // indirect
|
||||
github.com/blevesearch/vellum v1.1.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.2 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
|
@ -214,12 +214,12 @@ require (
|
|||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/libdns/libdns v0.2.3 // indirect
|
||||
github.com/libdns/libdns v1.0.0-beta.1 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/markbates/going v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.1 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
|
@ -253,7 +253,7 @@ require (
|
|||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
|
||||
|
|
121
go.sum
121
go.sum
|
@ -680,8 +680,8 @@ github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNx
|
|||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
|
||||
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
|
||||
github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
|
||||
github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2 h1:cSXom2MoKJ9KPPw29RoZtHvUETY4F4n/kXl8m9btnQ0=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2/go.mod h1:JitQWJ8JuV4Y87l8VsHiiwhb3cgdyn68mX40s7NT6PA=
|
||||
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
|
||||
|
@ -717,46 +717,46 @@ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd3
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/blevesearch/bleve/v2 v2.4.4 h1:RwwLGjUm54SwyyykbrZs4vc1qjzYic4ZnAnY9TwNl60=
|
||||
github.com/blevesearch/bleve/v2 v2.4.4/go.mod h1:fa2Eo6DP7JR+dMFpQe+WiZXINKSunh7WBtlDGbolKXk=
|
||||
github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY=
|
||||
github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4=
|
||||
github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk=
|
||||
github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY=
|
||||
github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
|
||||
github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w=
|
||||
github.com/blevesearch/go-faiss v1.0.24 h1:K79IvKjoKHdi7FdiXEsAhxpMuns0x4fM0BO93bW5jLI=
|
||||
github.com/blevesearch/go-faiss v1.0.24/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
|
||||
github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 h1:uGvKVvG7zvSxCwcm4/ehBa9cCEuZVE+/zvrSl57QUVY=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
|
||||
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
|
||||
github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE=
|
||||
github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
|
||||
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b h1:ju9Az5YgrzCeK3M1QwvZIpxYhChkXp7/L0RhDYsxXoE=
|
||||
github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b/go.mod h1:BlrYNpOu4BvVRslmIG+rLtKhmjIaRhIbG8sb9scGTwI=
|
||||
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
|
||||
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
|
||||
github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA=
|
||||
github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs=
|
||||
github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ=
|
||||
github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II=
|
||||
github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE=
|
||||
github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8=
|
||||
github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE=
|
||||
github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms=
|
||||
github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g=
|
||||
github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y=
|
||||
github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk=
|
||||
github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
|
@ -769,8 +769,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
|||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
|
||||
github.com/caddyserver/certmagic v0.22.2 h1:qzZURXlrxwR5m25/jpvVeEyJHeJJMvAwe5zlMufOTQk=
|
||||
github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs=
|
||||
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
||||
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
@ -893,8 +893,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
|
|||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
@ -1203,8 +1203,8 @@ github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j
|
|||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
|
@ -1228,8 +1228,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
|
|||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
|
||||
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
|
||||
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI=
|
||||
github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk=
|
||||
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||
|
@ -1252,12 +1252,12 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
|||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
|
||||
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY=
|
||||
github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g=
|
||||
github.com/mholt/acmez/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ=
|
||||
github.com/mholt/acmez/v3 v3.1.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
|
@ -1268,8 +1268,8 @@ github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY
|
|||
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.88 h1:v8MoIJjwYxOkehp+eiLIuvXk87P2raUtoU5klrAAshs=
|
||||
github.com/minio/minio-go/v7 v7.0.88/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg=
|
||||
github.com/minio/minio-go/v7 v7.0.90 h1:TmSj1083wtAD0kEYTx7a5pFsv3iRYMsOJ6A4crjA1lE=
|
||||
github.com/minio/minio-go/v7 v7.0.90/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -1454,8 +1454,8 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l
|
|||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
gitlab.com/gitlab-org/api/client-go v0.126.0 h1:VV5TdkF6pMbEdFGvbR2CwEgJwg6qdg1u3bj5eD2tiWk=
|
||||
gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
|
@ -1491,8 +1491,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
|||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
||||
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
|
@ -1514,8 +1514,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
|||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -1546,8 +1546,8 @@ golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeap
|
|||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -1647,8 +1647,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1678,8 +1678,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec
|
|||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -1700,8 +1700,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -1793,8 +1793,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -1809,8 +1809,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1831,8 +1831,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -2215,6 +2215,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -185,75 +185,6 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
|
|||
return err
|
||||
}
|
||||
|
||||
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
|
||||
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
|
||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
|
||||
// Find all runs in the specified repository, reference, and workflow with non-final status
|
||||
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
|
||||
RepoID: repoID,
|
||||
Ref: ref,
|
||||
WorkflowID: workflowID,
|
||||
TriggerEvent: event,
|
||||
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
|
||||
if total == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate over each found run and cancel its associated jobs.
|
||||
for _, run := range runs {
|
||||
// Find all jobs associated with the current run.
|
||||
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
|
||||
RunID: run.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Iterate over each job and attempt to cancel it.
|
||||
for _, job := range jobs {
|
||||
// Skip jobs that are already in a terminal state (completed, cancelled, etc.).
|
||||
status := job.Status
|
||||
if status.IsDone() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the job has no associated task (probably an error), set its status to 'Cancelled' and stop it.
|
||||
if job.TaskID == 0 {
|
||||
job.Status = StatusCancelled
|
||||
job.Stopped = timeutil.TimeStampNow()
|
||||
|
||||
// Update the job's status and stopped time in the database.
|
||||
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
|
||||
if n == 0 {
|
||||
return fmt.Errorf("job has changed, try again")
|
||||
}
|
||||
|
||||
// Continue with the next job.
|
||||
continue
|
||||
}
|
||||
|
||||
// If the job has an associated task, try to stop the task, effectively cancelling the job.
|
||||
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return nil to indicate successful cancellation of all running and waiting jobs.
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertRun inserts a run
|
||||
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
||||
|
|
|
@ -5,7 +5,6 @@ package actions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
|
@ -119,27 +118,6 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
|
|||
return committer.Commit()
|
||||
}
|
||||
|
||||
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository, cancelPreviousJobs bool) error {
|
||||
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
||||
// There is no other place we can do this because the app.ini will be changed manually
|
||||
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
||||
return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||
}
|
||||
if cancelPreviousJobs {
|
||||
// cancel running cron jobs of this repository and delete old schedules
|
||||
if err := CancelPreviousJobs(
|
||||
ctx,
|
||||
repo.ID,
|
||||
repo.DefaultBranch,
|
||||
"",
|
||||
webhook_module.HookEventSchedule,
|
||||
); err != nil {
|
||||
return fmt.Errorf("CancelPreviousJobs: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindScheduleOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
|
|
|
@ -17,10 +17,8 @@ import (
|
|||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
|
@ -337,140 +335,6 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// UpdateTaskByState updates the task by the state.
|
||||
// It will always update the task if the state is not final, even there is no change.
|
||||
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
|
||||
func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.TaskState) (*ActionTask, error) {
|
||||
stepStates := map[int64]*runnerv1.StepState{}
|
||||
for _, v := range state.Steps {
|
||||
stepStates[v.Id] = v
|
||||
}
|
||||
|
||||
ctx, commiter, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer commiter.Close()
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
task := &ActionTask{}
|
||||
if has, err := e.ID(state.Id).Get(task); err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, util.ErrNotExist
|
||||
} else if runnerID != task.RunnerID {
|
||||
return nil, fmt.Errorf("invalid runner for task")
|
||||
}
|
||||
|
||||
if task.Status.IsDone() {
|
||||
// the state is final, do nothing
|
||||
return task, nil
|
||||
}
|
||||
|
||||
// state.Result is not unspecified means the task is finished
|
||||
if state.Result != runnerv1.Result_RESULT_UNSPECIFIED {
|
||||
task.Status = Status(state.Result)
|
||||
task.Stopped = timeutil.TimeStamp(state.StoppedAt.AsTime().Unix())
|
||||
if err := UpdateTask(ctx, task, "status", "stopped"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := UpdateRunJob(ctx, &ActionRunJob{
|
||||
ID: task.JobID,
|
||||
Status: task.Status,
|
||||
Stopped: task.Stopped,
|
||||
}, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Force update ActionTask.Updated to avoid the task being judged as a zombie task
|
||||
task.Updated = timeutil.TimeStampNow()
|
||||
if err := UpdateTask(ctx, task, "updated"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := task.LoadAttributes(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, step := range task.Steps {
|
||||
var result runnerv1.Result
|
||||
if v, ok := stepStates[step.Index]; ok {
|
||||
result = v.Result
|
||||
step.LogIndex = v.LogIndex
|
||||
step.LogLength = v.LogLength
|
||||
step.Started = convertTimestamp(v.StartedAt)
|
||||
step.Stopped = convertTimestamp(v.StoppedAt)
|
||||
}
|
||||
if result != runnerv1.Result_RESULT_UNSPECIFIED {
|
||||
step.Status = Status(result)
|
||||
} else if step.Started != 0 {
|
||||
step.Status = StatusRunning
|
||||
}
|
||||
if _, err := e.ID(step.ID).Update(step); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := commiter.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func StopTask(ctx context.Context, taskID int64, status Status) error {
|
||||
if !status.IsDone() {
|
||||
return fmt.Errorf("cannot stop task with status %v", status)
|
||||
}
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
task := &ActionTask{}
|
||||
if has, err := e.ID(taskID).Get(task); err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
if task.Status.IsDone() {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := timeutil.TimeStampNow()
|
||||
task.Status = status
|
||||
task.Stopped = now
|
||||
if _, err := UpdateRunJob(ctx, &ActionRunJob{
|
||||
ID: task.JobID,
|
||||
Status: task.Status,
|
||||
Stopped: task.Stopped,
|
||||
}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := UpdateTask(ctx, task, "status", "stopped"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := task.LoadAttributes(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, step := range task.Steps {
|
||||
if !step.Status.IsDone() {
|
||||
step.Status = status
|
||||
if step.Started == 0 {
|
||||
step.Started = now
|
||||
}
|
||||
step.Stopped = now
|
||||
}
|
||||
if _, err := e.ID(step.ID).Update(step); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, limit int) ([]*ActionTask, error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
|
@ -481,13 +345,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
|
|||
Find(&tasks)
|
||||
}
|
||||
|
||||
func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
|
||||
if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
|
||||
return timeutil.TimeStamp(0)
|
||||
}
|
||||
return timeutil.TimeStamp(timestamp.AsTime().Unix())
|
||||
}
|
||||
|
||||
func logFileName(repoFullName string, taskID int64) string {
|
||||
ret := fmt.Sprintf("%s/%02x/%d.log", repoFullName, taskID%256, taskID)
|
||||
|
||||
|
|
|
@ -201,7 +201,7 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica
|
|||
}
|
||||
}
|
||||
|
||||
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
||||
if setting.Repository.Signing.Format == "openpgp" && setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
||||
// OK we should try the default key
|
||||
gpgSettings := git.GPGSettings{
|
||||
Sign: true,
|
||||
|
|
|
@ -12,8 +12,10 @@ import (
|
|||
"forgejo.org/models/db"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
"github.com/42wim/sshsig"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ParseObjectWithSSHSignature check if signature is good against keystore.
|
||||
|
@ -62,6 +64,22 @@ func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *u
|
|||
}
|
||||
}
|
||||
|
||||
// If the SSH instance key is set, try to verify it with that key.
|
||||
if setting.SSHInstanceKey != nil {
|
||||
instanceSSHKey := &PublicKey{
|
||||
Content: string(ssh.MarshalAuthorizedKey(setting.SSHInstanceKey)),
|
||||
Fingerprint: ssh.FingerprintSHA256(setting.SSHInstanceKey),
|
||||
}
|
||||
instanceUser := &user_model.User{
|
||||
Name: setting.Repository.Signing.SigningName,
|
||||
Email: setting.Repository.Signing.SigningEmail,
|
||||
}
|
||||
commitVerification := verifySSHObjectVerification(c.Signature.Signature, c.Signature.Payload, instanceSSHKey, committer, instanceUser, setting.Repository.Signing.SigningEmail)
|
||||
if commitVerification != nil {
|
||||
return commitVerification
|
||||
}
|
||||
}
|
||||
|
||||
return &ObjectVerification{
|
||||
CommittingUser: committer,
|
||||
Verified: false,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package asymkey
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
|
@ -15,6 +16,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestParseCommitWithSSHSignature(t *testing.T) {
|
||||
|
@ -150,4 +152,43 @@ muPLbvEduU+Ze/1Ol1pgk=
|
|||
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
||||
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
||||
})
|
||||
|
||||
t.Run("Instance key", func(t *testing.T) {
|
||||
pubKeyContent, err := os.ReadFile("../../tests/integration/ssh-signing-key.pub")
|
||||
require.NoError(t, err)
|
||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(pubKeyContent)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")()
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")()
|
||||
defer test.MockVariableValue(&setting.SSHInstanceKey, pubKey)()
|
||||
|
||||
gitCommit := &git.Commit{
|
||||
Committer: &git.Signature{
|
||||
Email: "fox@example.com",
|
||||
},
|
||||
Signature: &git.ObjectSignature{
|
||||
Payload: `tree f96f1a4f1a51dc42e2983592f503980b60b8849c
|
||||
parent 93f84db542dd8c6e952c8130bc2fcbe2e299b8b4
|
||||
author OwO <instance@example.com> 1738961379 +0100
|
||||
committer UwU <fox@example.com> 1738961379 +0100
|
||||
|
||||
Fox
|
||||
`,
|
||||
Signature: `-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgV5ELwZ8XJe2LLR/UTuEu/vsFdb
|
||||
t7ry0W8hyzz/b1iocAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
|
||||
AAAAQCnyMRkWVVNoZxZkvi/ZoknUhs4LNBmEwZs9e9214WIt+mhKfc6BiHoE2qeluR2McD
|
||||
Y5RzHnA8Ke9wXddEePCQE=
|
||||
-----END SSH SIGNATURE-----
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
o := commitToGitObject(gitCommit)
|
||||
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||
assert.True(t, commitVerification.Verified)
|
||||
assert.Equal(t, "UwU / SHA256:QttK41r/zMUeAW71b5UgVSb8xGFF/DlZJ6TyADW+uoI", commitVerification.Reason)
|
||||
assert.Equal(t, "SHA256:QttK41r/zMUeAW71b5UgVSb8xGFF/DlZJ6TyADW+uoI", commitVerification.SigningSSHKey.Fingerprint)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -283,6 +283,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
|
|||
return bitmap.toScope(), nil
|
||||
}
|
||||
|
||||
func (s AccessTokenScope) HasPermissionScope() bool {
|
||||
return s != "" && s != AccessTokenScopePublicOnly
|
||||
}
|
||||
|
||||
// PublicOnly checks if this token scope is limited to public resources
|
||||
func (s AccessTokenScope) PublicOnly() (bool, error) {
|
||||
bitmap, err := s.parse()
|
||||
|
|
17
models/fixtures/TestPackagesGetOrInsertBlob/package_blob.yml
Normal file
17
models/fixtures/TestPackagesGetOrInsertBlob/package_blob.yml
Normal 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
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
@ -19,18 +19,22 @@ type FederationHost struct {
|
|||
ID int64 `xorm:"pk autoincr"`
|
||||
HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"`
|
||||
NodeInfo NodeInfo `xorm:"extends NOT NULL"`
|
||||
HostPort uint16 `xorm:"NOT NULL DEFAULT 443"`
|
||||
HostSchema string `xorm:"NOT NULL DEFAULT 'https'"`
|
||||
LatestActivity time.Time `xorm:"NOT NULL"`
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
KeyID sql.NullString `xorm:"key_id UNIQUE"`
|
||||
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"`
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
// Factory function for FederationHost. Created struct is asserted to be valid.
|
||||
func NewFederationHost(nodeInfo NodeInfo, hostFqdn string) (FederationHost, error) {
|
||||
func NewFederationHost(hostFqdn string, nodeInfo NodeInfo, port uint16, schema string) (FederationHost, error) {
|
||||
result := FederationHost{
|
||||
HostFqdn: strings.ToLower(hostFqdn),
|
||||
NodeInfo: nodeInfo,
|
||||
HostPort: port,
|
||||
HostSchema: schema,
|
||||
}
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return FederationHost{}, err
|
||||
|
@ -43,6 +47,8 @@ func (host FederationHost) Validate() []string {
|
|||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(host.HostFqdn, "HostFqdn")...)
|
||||
result = append(result, validation.ValidateMaxLen(host.HostFqdn, 255, "HostFqdn")...)
|
||||
result = append(result, validation.ValidateNotEmpty(host.HostPort, "HostPort")...)
|
||||
result = append(result, validation.ValidateNotEmpty(host.HostSchema, "HostSchema")...)
|
||||
result = append(result, host.NodeInfo.Validate()...)
|
||||
if host.HostFqdn != strings.ToLower(host.HostFqdn) {
|
||||
result = append(result, fmt.Sprintf("HostFqdn has to be lower case but was: %v", host.HostFqdn))
|
||||
|
|
|
@ -6,7 +6,6 @@ package forgefed
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/validation"
|
||||
|
@ -44,8 +43,18 @@ func findFederationHostFromDB(ctx context.Context, searchKey, searchValue string
|
|||
return host, nil
|
||||
}
|
||||
|
||||
func FindFederationHostByFqdn(ctx context.Context, fqdn string) (*FederationHost, error) {
|
||||
return findFederationHostFromDB(ctx, "host_fqdn=?", strings.ToLower(fqdn))
|
||||
func FindFederationHostByFqdnAndPort(ctx context.Context, fqdn string, port uint16) (*FederationHost, error) {
|
||||
host := new(FederationHost)
|
||||
has, err := db.GetEngine(ctx).Where("host_fqdn=? AND host_port=?", fqdn, port).Get(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, nil
|
||||
}
|
||||
if res, err := validation.IsValid(host); !res {
|
||||
return nil, err
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func FindFederationHostByKeyID(ctx context.Context, keyID string) (*FederationHost, error) {
|
||||
|
|
|
@ -18,6 +18,8 @@ func Test_FederationHostValidation(t *testing.T) {
|
|||
SoftwareName: "forgejo",
|
||||
},
|
||||
LatestActivity: time.Now(),
|
||||
HostPort: 443,
|
||||
HostSchema: "https",
|
||||
}
|
||||
if res, err := validation.IsValid(sut); !res {
|
||||
t.Errorf("sut should be valid but was %q", err)
|
||||
|
@ -29,6 +31,8 @@ func Test_FederationHostValidation(t *testing.T) {
|
|||
SoftwareName: "forgejo",
|
||||
},
|
||||
LatestActivity: time.Now(),
|
||||
HostPort: 443,
|
||||
HostSchema: "https",
|
||||
}
|
||||
if res, _ := validation.IsValid(sut); res {
|
||||
t.Errorf("sut should be invalid: HostFqdn empty")
|
||||
|
@ -40,6 +44,8 @@ func Test_FederationHostValidation(t *testing.T) {
|
|||
SoftwareName: "forgejo",
|
||||
},
|
||||
LatestActivity: time.Now(),
|
||||
HostPort: 443,
|
||||
HostSchema: "https",
|
||||
}
|
||||
if res, _ := validation.IsValid(sut); res {
|
||||
t.Errorf("sut should be invalid: HostFqdn too long (len=256)")
|
||||
|
@ -49,6 +55,8 @@ func Test_FederationHostValidation(t *testing.T) {
|
|||
HostFqdn: "host.do.main",
|
||||
NodeInfo: NodeInfo{},
|
||||
LatestActivity: time.Now(),
|
||||
HostPort: 443,
|
||||
HostSchema: "https",
|
||||
}
|
||||
if res, _ := validation.IsValid(sut); res {
|
||||
t.Errorf("sut should be invalid: NodeInfo invalid")
|
||||
|
@ -60,6 +68,8 @@ func Test_FederationHostValidation(t *testing.T) {
|
|||
SoftwareName: "forgejo",
|
||||
},
|
||||
LatestActivity: time.Now().Add(1 * time.Hour),
|
||||
HostPort: 443,
|
||||
HostSchema: "https",
|
||||
}
|
||||
if res, _ := validation.IsValid(sut); res {
|
||||
t.Errorf("sut should be invalid: Future timestamp")
|
||||
|
@ -71,6 +81,8 @@ func Test_FederationHostValidation(t *testing.T) {
|
|||
SoftwareName: "forgejo",
|
||||
},
|
||||
LatestActivity: time.Now(),
|
||||
HostPort: 443,
|
||||
HostSchema: "https",
|
||||
}
|
||||
if res, _ := validation.IsValid(sut); res {
|
||||
t.Errorf("sut should be invalid: HostFqdn lower case")
|
||||
|
|
|
@ -96,6 +96,8 @@ var migrations = []*Migration{
|
|||
NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser),
|
||||
// v28 -> v29
|
||||
NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation),
|
||||
// v29 -> v30
|
||||
NewMigration("Migrate `User.NormalizedFederatedURI` column to extract port & schema into FederatedHost", MigrateNormalizedFederatedURI),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
|
|
|
@ -8,7 +8,7 @@ import "xorm.io/xorm"
|
|||
func AddHashBlake2bToPackageBlob(x *xorm.Engine) error {
|
||||
type PackageBlob struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
HashBlake2b string
|
||||
HashBlake2b string `xorm:"hash_blake2b char(128) UNIQUE(blake2b) INDEX"`
|
||||
}
|
||||
return x.Sync(&PackageBlob{})
|
||||
}
|
||||
|
|
106
models/forgejo_migrations/v30.go
Normal file
106
models/forgejo_migrations/v30.go
Normal 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()
|
||||
}
|
81
models/forgejo_migrations/v30_test.go
Normal file
81
models/forgejo_migrations/v30_test.go
Normal 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))
|
||||
}
|
|
@ -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"
|
|
@ -0,0 +1,5 @@
|
|||
-
|
||||
id: 1
|
||||
host_fqdn: "my.host.x"
|
||||
software_name: forgejo
|
||||
latest_activity: 2024-04-26 14:14:50
|
|
@ -0,0 +1,3 @@
|
|||
-
|
||||
id: 3
|
||||
normalized_federated_uri: "https://my.host.x/api/activitypub/user-id/18"
|
31
models/packages/main_test.go
Normal file
31
models/packages/main_test.go
Normal 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)
|
||||
}
|
|
@ -44,14 +44,19 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
|
|||
|
||||
existing := &PackageBlob{}
|
||||
|
||||
has, err := e.Where(builder.Eq{
|
||||
has, err := e.Where(builder.And(
|
||||
builder.Eq{
|
||||
"size": pb.Size,
|
||||
"hash_md5": pb.HashMD5,
|
||||
"hash_sha1": pb.HashSHA1,
|
||||
"hash_sha256": pb.HashSHA256,
|
||||
"hash_sha512": pb.HashSHA512,
|
||||
"hash_blake2b": pb.HashBlake2b,
|
||||
}).Get(existing)
|
||||
},
|
||||
builder.Or(
|
||||
builder.Eq{"hash_blake2b": pb.HashBlake2b},
|
||||
builder.IsNull{"hash_blake2b"},
|
||||
),
|
||||
)).Get(existing)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
|
65
models/packages/package_blob_test.go
Normal file
65
models/packages/package_blob_test.go
Normal 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})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -13,18 +13,9 @@ import (
|
|||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m)
|
||||
}
|
||||
|
||||
func prepareExamplePackage(t *testing.T) *packages_model.Package {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
|
|
@ -4,13 +4,10 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/validation"
|
||||
)
|
||||
|
||||
// APActorID returns the IRI to the api endpoint of the user
|
||||
|
@ -26,19 +23,3 @@ func (u *User) APActorID() string {
|
|||
func (u *User) APActorKeyID() string {
|
||||
return u.APActorID() + "#main-key"
|
||||
}
|
||||
|
||||
func GetUserByFederatedURI(ctx context.Context, federatedURI string) (*User, error) {
|
||||
user := new(User)
|
||||
has, err := db.GetEngine(ctx).Where("normalized_federated_uri=?", federatedURI).Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if res, err := validation.IsValid(*user); !res {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
|
|||
|
||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
||||
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||
if u.IsGhost() {
|
||||
if u.IsGhost() || u.ID <= 0 {
|
||||
return avatars.DefaultAvatarLink()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/validation"
|
||||
)
|
||||
|
||||
|
@ -18,13 +16,15 @@ type FederatedUser struct {
|
|||
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
|
||||
KeyID sql.NullString `xorm:"key_id UNIQUE"`
|
||||
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"`
|
||||
NormalizedOriginalURL string // This field ist just to keep original information. Pls. do not use for search or as ID!
|
||||
}
|
||||
|
||||
func NewFederatedUser(userID int64, externalID string, federationHostID int64) (FederatedUser, error) {
|
||||
func NewFederatedUser(userID int64, externalID string, federationHostID int64, normalizedOriginalURL string) (FederatedUser, error) {
|
||||
result := FederatedUser{
|
||||
UserID: userID,
|
||||
ExternalID: externalID,
|
||||
FederationHostID: federationHostID,
|
||||
NormalizedOriginalURL: normalizedOriginalURL,
|
||||
}
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return FederatedUser{}, err
|
||||
|
@ -32,30 +32,6 @@ func NewFederatedUser(userID int64, externalID string, federationHostID int64) (
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func getFederatedUserFromDB(ctx context.Context, searchKey, searchValue any) (*FederatedUser, error) {
|
||||
federatedUser := new(FederatedUser)
|
||||
has, err := db.GetEngine(ctx).Where(searchKey, searchValue).Get(federatedUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if res, err := validation.IsValid(*federatedUser); !res {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return federatedUser, nil
|
||||
}
|
||||
|
||||
func GetFederatedUserByKeyID(ctx context.Context, keyID string) (*FederatedUser, error) {
|
||||
return getFederatedUserFromDB(ctx, "key_id=?", keyID)
|
||||
}
|
||||
|
||||
func GetFederatedUserByUserID(ctx context.Context, userID int64) (*FederatedUser, error) {
|
||||
return getFederatedUserFromDB(ctx, "user_id=?", userID)
|
||||
}
|
||||
|
||||
func (user FederatedUser) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(user.UserID, "UserID")...)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
@ -135,9 +135,6 @@ type User struct {
|
|||
AvatarEmail string `xorm:"NOT NULL"`
|
||||
UseCustomAvatar bool
|
||||
|
||||
// For federation
|
||||
NormalizedFederatedURI string
|
||||
|
||||
// Counters
|
||||
NumFollowers int
|
||||
NumFollowing int `xorm:"NOT NULL DEFAULT 0"`
|
||||
|
|
|
@ -50,9 +50,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat
|
|||
return committer.Commit()
|
||||
}
|
||||
|
||||
func FindFederatedUser(ctx context.Context, externalID string,
|
||||
federationHostID int64,
|
||||
) (*User, *FederatedUser, error) {
|
||||
func FindFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) {
|
||||
federatedUser := new(FederatedUser)
|
||||
user := new(User)
|
||||
has, err := db.GetEngine(ctx).Where("external_id=? and federation_host_id=?", externalID, federationHostID).Get(federatedUser)
|
||||
|
@ -77,6 +75,32 @@ func FindFederatedUser(ctx context.Context, externalID string,
|
|||
return user, federatedUser, nil
|
||||
}
|
||||
|
||||
func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *FederatedUser, error) {
|
||||
federatedUser := new(FederatedUser)
|
||||
user := new(User)
|
||||
has, err := db.GetEngine(ctx).Where("key_id=?", keyID).Get(federatedUser)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if !has {
|
||||
return nil, nil, nil
|
||||
}
|
||||
has, err = db.GetEngine(ctx).ID(federatedUser.UserID).Get(user)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if !has {
|
||||
return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID)
|
||||
}
|
||||
|
||||
if res, err := validation.IsValid(*user); !res {
|
||||
return nil, nil, err
|
||||
}
|
||||
if res, err := validation.IsValid(*federatedUser); !res {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return user, federatedUser, nil
|
||||
}
|
||||
|
||||
func DeleteFederatedUser(ctx context.Context, userID int64) error {
|
||||
_, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID})
|
||||
return err
|
||||
|
|
|
@ -91,7 +91,7 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||
func TestCreateWebhook(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "www.example.com/unit_test",
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
@ -6,6 +6,7 @@ package forgefed
|
|||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
|
@ -17,11 +18,12 @@ import (
|
|||
type ActorID struct {
|
||||
ID string
|
||||
Source string
|
||||
Schema string
|
||||
HostSchema string
|
||||
Path string
|
||||
Host string
|
||||
Port string
|
||||
HostPort uint16
|
||||
UnvalidatedInput string
|
||||
IsPortSupplemented bool
|
||||
}
|
||||
|
||||
// Factory function for ActorID. Created struct is asserted to be valid
|
||||
|
@ -40,20 +42,23 @@ func NewActorID(uri string) (ActorID, error) {
|
|||
|
||||
func (id ActorID) AsURI() string {
|
||||
var result string
|
||||
if id.Port == "" {
|
||||
result = fmt.Sprintf("%s://%s/%s/%s", id.Schema, id.Host, id.Path, id.ID)
|
||||
|
||||
if id.IsPortSupplemented {
|
||||
result = fmt.Sprintf("%s://%s/%s/%s", id.HostSchema, id.Host, id.Path, id.ID)
|
||||
} else {
|
||||
result = fmt.Sprintf("%s://%s:%s/%s/%s", id.Schema, id.Host, id.Port, id.Path, id.ID)
|
||||
result = fmt.Sprintf("%s://%s:%d/%s/%s", id.HostSchema, id.Host, id.HostPort, id.Path, id.ID)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (id ActorID) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.Schema, "schema")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.Host, "host")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.HostPort, "hostPort")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.HostSchema, "hostSchema")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.UnvalidatedInput, "unvalidatedInput")...)
|
||||
|
||||
if id.UnvalidatedInput != id.AsURI() {
|
||||
|
@ -104,12 +109,14 @@ func (id PersonID) Validate() []string {
|
|||
result := id.ActorID.Validate()
|
||||
result = append(result, validation.ValidateNotEmpty(id.Source, "source")...)
|
||||
result = append(result, validation.ValidateOneOf(id.Source, []any{"forgejo", "gitea"}, "Source")...)
|
||||
|
||||
switch id.Source {
|
||||
case "forgejo", "gitea":
|
||||
if strings.ToLower(id.Path) != "api/v1/activitypub/user-id" && strings.ToLower(id.Path) != "api/activitypub/user-id" {
|
||||
result = append(result, fmt.Sprintf("path: %q has to be a person specific api path", id.Path))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -168,6 +175,8 @@ func removeEmptyStrings(ls []string) []string {
|
|||
return rs
|
||||
}
|
||||
|
||||
// ----------------------------- newActorID --------------------------------------------
|
||||
|
||||
func newActorID(uri string) (ActorID, error) {
|
||||
validatedURI, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
|
@ -179,15 +188,27 @@ func newActorID(uri string) (ActorID, error) {
|
|||
}
|
||||
length := len(pathWithActorID)
|
||||
pathWithoutActorID := strings.Join(pathWithActorID[0:length-1], "/")
|
||||
id := pathWithActorID[length-1]
|
||||
id := strings.ToLower(pathWithActorID[length-1])
|
||||
|
||||
result := ActorID{}
|
||||
result.ID = id
|
||||
result.Schema = validatedURI.Scheme
|
||||
result.Host = validatedURI.Hostname()
|
||||
result.Path = pathWithoutActorID
|
||||
result.Port = validatedURI.Port()
|
||||
result.UnvalidatedInput = uri
|
||||
result.HostSchema = strings.ToLower(validatedURI.Scheme)
|
||||
result.Host = strings.ToLower(validatedURI.Hostname())
|
||||
result.Path = strings.ToLower(pathWithoutActorID)
|
||||
|
||||
if validatedURI.Port() == "" && result.HostSchema == "https" {
|
||||
result.IsPortSupplemented = true
|
||||
result.HostPort = 443
|
||||
} else if validatedURI.Port() == "" && result.HostSchema == "http" {
|
||||
result.IsPortSupplemented = true
|
||||
result.HostPort = 80
|
||||
} else {
|
||||
numPort, _ := strconv.ParseUint(validatedURI.Port(), 10, 16)
|
||||
result.HostPort = uint16(numPort)
|
||||
}
|
||||
|
||||
result.UnvalidatedInput = strings.ToLower(uri)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023, 2024 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
@ -18,11 +18,13 @@ func TestNewPersonId(t *testing.T) {
|
|||
expected := PersonID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.Schema = "https"
|
||||
expected.HostSchema = "https"
|
||||
expected.Path = "api/v1/activitypub/user-id"
|
||||
expected.Host = "an.other.host"
|
||||
expected.Port = ""
|
||||
expected.HostPort = 443
|
||||
expected.IsPortSupplemented = true
|
||||
expected.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
||||
|
||||
sut, _ := NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
|
||||
if sut != expected {
|
||||
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
|
||||
|
@ -31,15 +33,47 @@ func TestNewPersonId(t *testing.T) {
|
|||
expected = PersonID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.Schema = "https"
|
||||
expected.HostSchema = "https"
|
||||
expected.Path = "api/v1/activitypub/user-id"
|
||||
expected.Host = "an.other.host"
|
||||
expected.Port = "443"
|
||||
expected.HostPort = 443
|
||||
expected.IsPortSupplemented = false
|
||||
expected.UnvalidatedInput = "https://an.other.host:443/api/v1/activitypub/user-id/1"
|
||||
|
||||
sut, _ = NewPersonID("https://an.other.host:443/api/v1/activitypub/user-id/1", "forgejo")
|
||||
if sut != expected {
|
||||
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
|
||||
}
|
||||
|
||||
expected = PersonID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.HostSchema = "http"
|
||||
expected.Path = "api/v1/activitypub/user-id"
|
||||
expected.Host = "an.other.host"
|
||||
expected.HostPort = 80
|
||||
expected.IsPortSupplemented = false
|
||||
expected.UnvalidatedInput = "http://an.other.host:80/api/v1/activitypub/user-id/1"
|
||||
|
||||
sut, _ = NewPersonID("http://an.other.host:80/api/v1/activitypub/user-id/1", "forgejo")
|
||||
if sut != expected {
|
||||
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
|
||||
}
|
||||
|
||||
expected = PersonID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.HostSchema = "https"
|
||||
expected.Path = "api/v1/activitypub/user-id"
|
||||
expected.Host = "an.other.host"
|
||||
expected.HostPort = 443
|
||||
expected.IsPortSupplemented = false
|
||||
expected.UnvalidatedInput = "https://an.other.host:443/api/v1/activitypub/user-id/1"
|
||||
|
||||
sut, _ = NewPersonID("HTTPS://an.other.host:443/api/v1/activitypub/user-id/1", "forgejo")
|
||||
if sut != expected {
|
||||
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRepositoryId(t *testing.T) {
|
||||
|
@ -47,10 +81,11 @@ func TestNewRepositoryId(t *testing.T) {
|
|||
expected := RepositoryID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.Schema = "http"
|
||||
expected.HostSchema = "http"
|
||||
expected.Path = "api/activitypub/repository-id"
|
||||
expected.Host = "localhost"
|
||||
expected.Port = "3000"
|
||||
expected.HostPort = 3000
|
||||
expected.IsPortSupplemented = false
|
||||
expected.UnvalidatedInput = "http://localhost:3000/api/activitypub/repository-id/1"
|
||||
sut, _ := NewRepositoryID("http://localhost:3000/api/activitypub/repository-id/1", "forgejo")
|
||||
if sut != expected {
|
||||
|
@ -61,10 +96,11 @@ func TestNewRepositoryId(t *testing.T) {
|
|||
func TestActorIdValidation(t *testing.T) {
|
||||
sut := ActorID{}
|
||||
sut.Source = "forgejo"
|
||||
sut.Schema = "https"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "api/v1/activitypub/user-id"
|
||||
sut.Host = "an.other.host"
|
||||
sut.Port = ""
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/"
|
||||
if sut.Validate()[0] != "userId should not be empty" {
|
||||
t.Errorf("validation error expected but was: %v\n", sut.Validate())
|
||||
|
@ -73,10 +109,11 @@ func TestActorIdValidation(t *testing.T) {
|
|||
sut = ActorID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejo"
|
||||
sut.Schema = "https"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "api/v1/activitypub/user-id"
|
||||
sut.Host = "an.other.host"
|
||||
sut.Port = ""
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1?illegal=action"
|
||||
if sut.Validate()[0] != "not all input was parsed, \nUnvalidated Input:\"https://an.other.host/api/v1/activitypub/user-id/1?illegal=action\" \nParsed URI: \"https://an.other.host/api/v1/activitypub/user-id/1\"" {
|
||||
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
|
||||
|
@ -87,10 +124,11 @@ func TestPersonIdValidation(t *testing.T) {
|
|||
sut := PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejo"
|
||||
sut.Schema = "https"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "path"
|
||||
sut.Host = "an.other.host"
|
||||
sut.Port = ""
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/path/1"
|
||||
|
||||
_, err := validation.IsValid(sut)
|
||||
|
@ -101,10 +139,11 @@ func TestPersonIdValidation(t *testing.T) {
|
|||
sut = PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejox"
|
||||
sut.Schema = "https"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "api/v1/activitypub/user-id"
|
||||
sut.Host = "an.other.host"
|
||||
sut.Port = ""
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
||||
if sut.Validate()[0] != "Value forgejox is not contained in allowed values [forgejo gitea]" {
|
||||
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
|
||||
|
@ -125,12 +164,10 @@ func TestWebfingerId(t *testing.T) {
|
|||
|
||||
func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
|
||||
var err any
|
||||
// TODO: remove after test
|
||||
//_, err = NewPersonId("", "forgejo")
|
||||
//if err == nil {
|
||||
// t.Errorf("empty input should be invalid.")
|
||||
//}
|
||||
|
||||
_, err = NewPersonID("", "forgejo")
|
||||
if err == nil {
|
||||
t.Errorf("empty input should be invalid.")
|
||||
}
|
||||
_, err = NewPersonID("http://localhost:3000/api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Errorf("localhost uris are not external")
|
||||
|
@ -155,7 +192,6 @@ func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Errorf("uri may not contain unparsed elements")
|
||||
}
|
||||
|
||||
_, err = NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
|
||||
if err != nil {
|
||||
t.Errorf("this uri should be valid but was: %v", err)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2023, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
@ -10,10 +10,10 @@ import (
|
|||
func (id ActorID) AsWellKnownNodeInfoURI() string {
|
||||
wellKnownPath := ".well-known/nodeinfo"
|
||||
var result string
|
||||
if id.Port == "" {
|
||||
result = fmt.Sprintf("%s://%s/%s", id.Schema, id.Host, wellKnownPath)
|
||||
if id.HostPort == 0 {
|
||||
result = fmt.Sprintf("%s://%s/%s", id.HostSchema, id.Host, wellKnownPath)
|
||||
} else {
|
||||
result = fmt.Sprintf("%s://%s:%s/%s", id.Schema, id.Host, id.Port, wellKnownPath)
|
||||
result = fmt.Sprintf("%s://%s:%d/%s", id.HostSchema, id.Host, id.HostPort, wellKnownPath)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -432,6 +432,11 @@ func (c *Commit) GetBranchName() (string, error) {
|
|||
return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil
|
||||
}
|
||||
|
||||
// GetAllBranches returns a slice with all branches that contains this commit
|
||||
func (c *Commit) GetAllBranches() ([]string, error) {
|
||||
return c.repo.getBranches(c, -1)
|
||||
}
|
||||
|
||||
// CommitFileStatus represents status of files in a commit.
|
||||
type CommitFileStatus struct {
|
||||
Added []string
|
||||
|
|
|
@ -5,6 +5,7 @@ package git
|
|||
|
||||
import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -370,6 +371,23 @@ func TestParseCommitRenames(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetAllBranches(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
|
||||
require.NoError(t, err)
|
||||
|
||||
commit, err := bareRepo1.GetCommit("95bb4d39648ee7e325106df01a621c530863a653")
|
||||
require.NoError(t, err)
|
||||
|
||||
branches, err := commit.GetAllBranches()
|
||||
require.NoError(t, err)
|
||||
|
||||
slices.Sort(branches)
|
||||
|
||||
assert.Equal(t, []string{"branch1", "branch2", "master"}, branches)
|
||||
}
|
||||
|
||||
func Test_parseSubmoduleContent(t *testing.T) {
|
||||
submoduleFiles := []struct {
|
||||
fileContent string
|
||||
|
|
|
@ -278,6 +278,49 @@ func syncGitConfig() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
switch setting.Repository.Signing.Format {
|
||||
case "ssh":
|
||||
// First do a git version check.
|
||||
if CheckGitVersionAtLeast("2.34.0") != nil {
|
||||
return errors.New("ssh signing requires Git >= 2.34.0")
|
||||
}
|
||||
|
||||
// Get the ssh-keygen binary that Git will use.
|
||||
// This can be overriden in app.ini in [git.config] section, so we must
|
||||
// query this information.
|
||||
sshKeygenPath, err := configGet("gpg.ssh.program")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// git is very stubborn and does not give a default value, so we must do
|
||||
// this ourselves.
|
||||
if len(sshKeygenPath) == 0 {
|
||||
// Default value of git, very unlikely to change.
|
||||
// https://github.com/git/git/blob/5b97a56fa0e7d580dc8865b73107407c9b3f0eff/gpg-interface.c#L116
|
||||
sshKeygenPath = "ssh-keygen"
|
||||
}
|
||||
|
||||
// Although there's a version requirement of 8.2p1, there's no cross-version
|
||||
// method to get the version of ssh-keygen. Therefore we do a simple binary
|
||||
// presence check and hope for the best.
|
||||
if _, err := exec.LookPath(sshKeygenPath); err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
return errors.New("git signing requires a ssh-keygen binary")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := configSet("gpg.format", "ssh"); err != nil {
|
||||
return err
|
||||
}
|
||||
// openpgp is already the default value, so in the case of a non SSH format
|
||||
// set the value to openpgp.
|
||||
default:
|
||||
if err := configSet("gpg.format", "openpgp"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||
|
@ -324,6 +367,15 @@ func CheckGitVersionEqual(equal string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func configGet(key string) (string, error) {
|
||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
if err != nil && !IsErrorExitCode(err, 1) {
|
||||
return "", fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
func configSet(key, value string) error {
|
||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||
if err != nil && !IsErrorExitCode(err, 1) {
|
||||
|
|
|
@ -11,8 +11,10 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -94,3 +96,57 @@ func TestSyncConfig(t *testing.T) {
|
|||
assert.True(t, gitConfigContains("[sync-test]"))
|
||||
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
|
||||
}
|
||||
|
||||
func TestSyncConfigGPGFormat(t *testing.T) {
|
||||
defer test.MockProtect(&setting.GitConfig)()
|
||||
|
||||
t.Run("No format", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "")()
|
||||
require.NoError(t, syncGitConfig())
|
||||
assert.True(t, gitConfigContains("[gpg]"))
|
||||
assert.True(t, gitConfigContains("format = openpgp"))
|
||||
})
|
||||
|
||||
t.Run("SSH format", func(t *testing.T) {
|
||||
r, err := os.OpenRoot(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700)
|
||||
require.NoError(t, f.Close())
|
||||
require.NoError(t, err)
|
||||
t.Setenv("PATH", r.Name())
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "ssh")()
|
||||
|
||||
require.NoError(t, syncGitConfig())
|
||||
assert.True(t, gitConfigContains("[gpg]"))
|
||||
assert.True(t, gitConfigContains("format = ssh"))
|
||||
|
||||
t.Run("Old version", func(t *testing.T) {
|
||||
oldVersion, err := version.NewVersion("2.33.0")
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(&gitVersion, oldVersion)()
|
||||
require.ErrorContains(t, syncGitConfig(), "ssh signing requires Git >= 2.34.0")
|
||||
})
|
||||
|
||||
t.Run("No ssh-keygen binary", func(t *testing.T) {
|
||||
require.NoError(t, r.Remove("ssh-keygen"))
|
||||
require.ErrorContains(t, syncGitConfig(), "git signing requires a ssh-keygen binary")
|
||||
})
|
||||
|
||||
t.Run("Dynamic ssh-keygen binary location", func(t *testing.T) {
|
||||
f, err := r.OpenFile("ssh-keygen-2", os.O_CREATE|os.O_TRUNC, 0o700)
|
||||
require.NoError(t, f.Close())
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(&setting.GitConfig.Options, map[string]string{
|
||||
"gpg.ssh.program": "ssh-keygen-2",
|
||||
})()
|
||||
require.NoError(t, syncGitConfig())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("OpenPGP format", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")()
|
||||
require.NoError(t, syncGitConfig())
|
||||
assert.True(t, gitConfigContains("[gpg]"))
|
||||
assert.True(t, gitConfigContains("format = openpgp"))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -444,10 +444,13 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
|
|||
|
||||
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
|
||||
if CheckGitVersionAtLeast("2.7.0") == nil {
|
||||
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").
|
||||
AddOptionFormat("--count=%d", limit).
|
||||
AddOptionValues("--contains", commit.ID.String(), BranchPrefix).
|
||||
RunStdString(&RunOpts{Dir: repo.Path})
|
||||
command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix)
|
||||
|
||||
if limit != -1 {
|
||||
command = command.AddOptionFormat("--count=%d", limit)
|
||||
}
|
||||
|
||||
stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -260,11 +260,11 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
|||
if opts.Mode == internal.CodeSearchModeUnion {
|
||||
query := bleve.NewDisjunctionQuery()
|
||||
for _, field := range strings.Fields(opts.Keyword) {
|
||||
query.AddQuery(inner_bleve.MatchPhraseQuery(field, "Content", repoIndexerAnalyzer, 0))
|
||||
query.AddQuery(inner_bleve.MatchPhraseQuery(field, "Content", repoIndexerAnalyzer, false))
|
||||
}
|
||||
keywordQuery = query
|
||||
} else {
|
||||
keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, 0)
|
||||
keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, false)
|
||||
}
|
||||
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
|
|
|
@ -65,5 +65,7 @@ func TokenizerConstructor(config map[string]any, cache *registry.Cache) (analysi
|
|||
}
|
||||
|
||||
func init() {
|
||||
registry.RegisterTokenizer(Name, TokenizerConstructor)
|
||||
if err := registry.RegisterTokenizer(Name, TokenizerConstructor); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,11 @@ func MatchQuery(matchTerm, field, analyzer string, fuzziness int) *query.MatchQu
|
|||
}
|
||||
|
||||
// MatchPhraseQuery generates a match phrase query for the given phrase, field and analyzer
|
||||
func MatchPhraseQuery(matchPhrase, field, analyzer string, fuzziness int) *query.MatchPhraseQuery {
|
||||
func MatchPhraseQuery(matchPhrase, field, analyzer string, autoFuzzy bool) *query.MatchPhraseQuery {
|
||||
q := bleve.NewMatchPhraseQuery(matchPhrase)
|
||||
q.FieldVal = field
|
||||
q.Analyzer = analyzer
|
||||
q.Fuzziness = fuzziness
|
||||
q.SetAutoFuzziness(autoFuzzy)
|
||||
return q
|
||||
}
|
||||
|
||||
|
|
|
@ -162,15 +162,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
}
|
||||
q := bleve.NewBooleanQuery()
|
||||
for _, token := range tokens {
|
||||
fuzziness := 0
|
||||
if token.Fuzzy {
|
||||
// TODO: replace with "auto" after bleve update
|
||||
fuzziness = min(len(token.Term)/4, 2)
|
||||
}
|
||||
innerQ := bleve.NewDisjunctionQuery(
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, fuzziness),
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, fuzziness),
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, fuzziness))
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, token.Fuzzy),
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, token.Fuzzy),
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, token.Fuzzy))
|
||||
|
||||
switch token.Kind {
|
||||
case internal.BoolOptMust:
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
type MockRedisClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockRedisClientMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockRedisClientMockRecorder is the mock recorder for MockRedisClient.
|
||||
|
@ -56,38 +57,38 @@ func (mr *MockRedisClientMockRecorder) Close() *gomock.Call {
|
|||
}
|
||||
|
||||
// DBSize mocks base method.
|
||||
func (m *MockRedisClient) DBSize(arg0 context.Context) *redis.IntCmd {
|
||||
func (m *MockRedisClient) DBSize(ctx context.Context) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DBSize", arg0)
|
||||
ret := m.ctrl.Call(m, "DBSize", ctx)
|
||||
ret0, _ := ret[0].(*redis.IntCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DBSize indicates an expected call of DBSize.
|
||||
func (mr *MockRedisClientMockRecorder) DBSize(arg0 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) DBSize(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBSize", reflect.TypeOf((*MockRedisClient)(nil).DBSize), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBSize", reflect.TypeOf((*MockRedisClient)(nil).DBSize), ctx)
|
||||
}
|
||||
|
||||
// Decr mocks base method.
|
||||
func (m *MockRedisClient) Decr(arg0 context.Context, arg1 string) *redis.IntCmd {
|
||||
func (m *MockRedisClient) Decr(ctx context.Context, key string) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Decr", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "Decr", ctx, key)
|
||||
ret0, _ := ret[0].(*redis.IntCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Decr indicates an expected call of Decr.
|
||||
func (mr *MockRedisClientMockRecorder) Decr(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) Decr(ctx, key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decr", reflect.TypeOf((*MockRedisClient)(nil).Decr), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decr", reflect.TypeOf((*MockRedisClient)(nil).Decr), ctx, key)
|
||||
}
|
||||
|
||||
// Del mocks base method.
|
||||
func (m *MockRedisClient) Del(arg0 context.Context, arg1 ...string) *redis.IntCmd {
|
||||
func (m *MockRedisClient) Del(ctx context.Context, keys ...string) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs := []any{ctx}
|
||||
for _, a := range keys {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Del", varargs...)
|
||||
|
@ -96,17 +97,17 @@ func (m *MockRedisClient) Del(arg0 context.Context, arg1 ...string) *redis.IntCm
|
|||
}
|
||||
|
||||
// Del indicates an expected call of Del.
|
||||
func (mr *MockRedisClientMockRecorder) Del(arg0 any, arg1 ...any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) Del(ctx any, keys ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0}, arg1...)
|
||||
varargs := append([]any{ctx}, keys...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockRedisClient)(nil).Del), varargs...)
|
||||
}
|
||||
|
||||
// Exists mocks base method.
|
||||
func (m *MockRedisClient) Exists(arg0 context.Context, arg1 ...string) *redis.IntCmd {
|
||||
func (m *MockRedisClient) Exists(ctx context.Context, keys ...string) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs := []any{ctx}
|
||||
for _, a := range keys {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Exists", varargs...)
|
||||
|
@ -115,45 +116,45 @@ func (m *MockRedisClient) Exists(arg0 context.Context, arg1 ...string) *redis.In
|
|||
}
|
||||
|
||||
// Exists indicates an expected call of Exists.
|
||||
func (mr *MockRedisClientMockRecorder) Exists(arg0 any, arg1 ...any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) Exists(ctx any, keys ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0}, arg1...)
|
||||
varargs := append([]any{ctx}, keys...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockRedisClient)(nil).Exists), varargs...)
|
||||
}
|
||||
|
||||
// FlushDB mocks base method.
|
||||
func (m *MockRedisClient) FlushDB(arg0 context.Context) *redis.StatusCmd {
|
||||
func (m *MockRedisClient) FlushDB(ctx context.Context) *redis.StatusCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FlushDB", arg0)
|
||||
ret := m.ctrl.Call(m, "FlushDB", ctx)
|
||||
ret0, _ := ret[0].(*redis.StatusCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// FlushDB indicates an expected call of FlushDB.
|
||||
func (mr *MockRedisClientMockRecorder) FlushDB(arg0 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) FlushDB(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushDB", reflect.TypeOf((*MockRedisClient)(nil).FlushDB), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushDB", reflect.TypeOf((*MockRedisClient)(nil).FlushDB), ctx)
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
func (m *MockRedisClient) Get(arg0 context.Context, arg1 string) *redis.StringCmd {
|
||||
func (m *MockRedisClient) Get(ctx context.Context, key string) *redis.StringCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "Get", ctx, key)
|
||||
ret0, _ := ret[0].(*redis.StringCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get.
|
||||
func (mr *MockRedisClientMockRecorder) Get(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) Get(ctx, key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisClient)(nil).Get), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisClient)(nil).Get), ctx, key)
|
||||
}
|
||||
|
||||
// HDel mocks base method.
|
||||
func (m *MockRedisClient) HDel(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntCmd {
|
||||
func (m *MockRedisClient) HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs := []any{ctx, key}
|
||||
for _, a := range fields {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "HDel", varargs...)
|
||||
|
@ -162,31 +163,31 @@ func (m *MockRedisClient) HDel(arg0 context.Context, arg1 string, arg2 ...string
|
|||
}
|
||||
|
||||
// HDel indicates an expected call of HDel.
|
||||
func (mr *MockRedisClientMockRecorder) HDel(arg0, arg1 any, arg2 ...any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) HDel(ctx, key any, fields ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
varargs := append([]any{ctx, key}, fields...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HDel", reflect.TypeOf((*MockRedisClient)(nil).HDel), varargs...)
|
||||
}
|
||||
|
||||
// HKeys mocks base method.
|
||||
func (m *MockRedisClient) HKeys(arg0 context.Context, arg1 string) *redis.StringSliceCmd {
|
||||
func (m *MockRedisClient) HKeys(ctx context.Context, key string) *redis.StringSliceCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HKeys", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "HKeys", ctx, key)
|
||||
ret0, _ := ret[0].(*redis.StringSliceCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HKeys indicates an expected call of HKeys.
|
||||
func (mr *MockRedisClientMockRecorder) HKeys(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) HKeys(ctx, key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HKeys", reflect.TypeOf((*MockRedisClient)(nil).HKeys), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HKeys", reflect.TypeOf((*MockRedisClient)(nil).HKeys), ctx, key)
|
||||
}
|
||||
|
||||
// HSet mocks base method.
|
||||
func (m *MockRedisClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
|
||||
func (m *MockRedisClient) HSet(ctx context.Context, key string, values ...any) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs := []any{ctx, key}
|
||||
for _, a := range values {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "HSet", varargs...)
|
||||
|
@ -195,73 +196,73 @@ func (m *MockRedisClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) *
|
|||
}
|
||||
|
||||
// HSet indicates an expected call of HSet.
|
||||
func (mr *MockRedisClientMockRecorder) HSet(arg0, arg1 any, arg2 ...any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) HSet(ctx, key any, values ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
varargs := append([]any{ctx, key}, values...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSet", reflect.TypeOf((*MockRedisClient)(nil).HSet), varargs...)
|
||||
}
|
||||
|
||||
// Incr mocks base method.
|
||||
func (m *MockRedisClient) Incr(arg0 context.Context, arg1 string) *redis.IntCmd {
|
||||
func (m *MockRedisClient) Incr(ctx context.Context, key string) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Incr", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "Incr", ctx, key)
|
||||
ret0, _ := ret[0].(*redis.IntCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Incr indicates an expected call of Incr.
|
||||
func (mr *MockRedisClientMockRecorder) Incr(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) Incr(ctx, key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisClient)(nil).Incr), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisClient)(nil).Incr), ctx, key)
|
||||
}
|
||||
|
||||
// LLen mocks base method.
|
||||
func (m *MockRedisClient) LLen(arg0 context.Context, arg1 string) *redis.IntCmd {
|
||||
func (m *MockRedisClient) LLen(ctx context.Context, key string) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LLen", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "LLen", ctx, key)
|
||||
ret0, _ := ret[0].(*redis.IntCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// LLen indicates an expected call of LLen.
|
||||
func (mr *MockRedisClientMockRecorder) LLen(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) LLen(ctx, key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLen", reflect.TypeOf((*MockRedisClient)(nil).LLen), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLen", reflect.TypeOf((*MockRedisClient)(nil).LLen), ctx, key)
|
||||
}
|
||||
|
||||
// LPop mocks base method.
|
||||
func (m *MockRedisClient) LPop(arg0 context.Context, arg1 string) *redis.StringCmd {
|
||||
func (m *MockRedisClient) LPop(ctx context.Context, key string) *redis.StringCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LPop", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "LPop", ctx, key)
|
||||
ret0, _ := ret[0].(*redis.StringCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// LPop indicates an expected call of LPop.
|
||||
func (mr *MockRedisClientMockRecorder) LPop(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) LPop(ctx, key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LPop", reflect.TypeOf((*MockRedisClient)(nil).LPop), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LPop", reflect.TypeOf((*MockRedisClient)(nil).LPop), ctx, key)
|
||||
}
|
||||
|
||||
// Ping mocks base method.
|
||||
func (m *MockRedisClient) Ping(arg0 context.Context) *redis.StatusCmd {
|
||||
func (m *MockRedisClient) Ping(ctx context.Context) *redis.StatusCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Ping", arg0)
|
||||
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||
ret0, _ := ret[0].(*redis.StatusCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Ping indicates an expected call of Ping.
|
||||
func (mr *MockRedisClientMockRecorder) Ping(arg0 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) Ping(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockRedisClient)(nil).Ping), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockRedisClient)(nil).Ping), ctx)
|
||||
}
|
||||
|
||||
// RPush mocks base method.
|
||||
func (m *MockRedisClient) RPush(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
|
||||
func (m *MockRedisClient) RPush(ctx context.Context, key string, values ...any) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs := []any{ctx, key}
|
||||
for _, a := range values {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "RPush", varargs...)
|
||||
|
@ -270,17 +271,17 @@ func (m *MockRedisClient) RPush(arg0 context.Context, arg1 string, arg2 ...any)
|
|||
}
|
||||
|
||||
// RPush indicates an expected call of RPush.
|
||||
func (mr *MockRedisClientMockRecorder) RPush(arg0, arg1 any, arg2 ...any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) RPush(ctx, key any, values ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
varargs := append([]any{ctx, key}, values...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RPush", reflect.TypeOf((*MockRedisClient)(nil).RPush), varargs...)
|
||||
}
|
||||
|
||||
// SAdd mocks base method.
|
||||
func (m *MockRedisClient) SAdd(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
|
||||
func (m *MockRedisClient) SAdd(ctx context.Context, key string, members ...any) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs := []any{ctx, key}
|
||||
for _, a := range members {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SAdd", varargs...)
|
||||
|
@ -289,31 +290,31 @@ func (m *MockRedisClient) SAdd(arg0 context.Context, arg1 string, arg2 ...any) *
|
|||
}
|
||||
|
||||
// SAdd indicates an expected call of SAdd.
|
||||
func (mr *MockRedisClientMockRecorder) SAdd(arg0, arg1 any, arg2 ...any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) SAdd(ctx, key any, members ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
varargs := append([]any{ctx, key}, members...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SAdd", reflect.TypeOf((*MockRedisClient)(nil).SAdd), varargs...)
|
||||
}
|
||||
|
||||
// SIsMember mocks base method.
|
||||
func (m *MockRedisClient) SIsMember(arg0 context.Context, arg1 string, arg2 any) *redis.BoolCmd {
|
||||
func (m *MockRedisClient) SIsMember(ctx context.Context, key string, member any) *redis.BoolCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SIsMember", arg0, arg1, arg2)
|
||||
ret := m.ctrl.Call(m, "SIsMember", ctx, key, member)
|
||||
ret0, _ := ret[0].(*redis.BoolCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SIsMember indicates an expected call of SIsMember.
|
||||
func (mr *MockRedisClientMockRecorder) SIsMember(arg0, arg1, arg2 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) SIsMember(ctx, key, member any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SIsMember", reflect.TypeOf((*MockRedisClient)(nil).SIsMember), arg0, arg1, arg2)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SIsMember", reflect.TypeOf((*MockRedisClient)(nil).SIsMember), ctx, key, member)
|
||||
}
|
||||
|
||||
// SRem mocks base method.
|
||||
func (m *MockRedisClient) SRem(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
|
||||
func (m *MockRedisClient) SRem(ctx context.Context, key string, members ...any) *redis.IntCmd {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs := []any{ctx, key}
|
||||
for _, a := range members {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SRem", varargs...)
|
||||
|
@ -322,22 +323,22 @@ func (m *MockRedisClient) SRem(arg0 context.Context, arg1 string, arg2 ...any) *
|
|||
}
|
||||
|
||||
// SRem indicates an expected call of SRem.
|
||||
func (mr *MockRedisClientMockRecorder) SRem(arg0, arg1 any, arg2 ...any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) SRem(ctx, key any, members ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{arg0, arg1}, arg2...)
|
||||
varargs := append([]any{ctx, key}, members...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SRem", reflect.TypeOf((*MockRedisClient)(nil).SRem), varargs...)
|
||||
}
|
||||
|
||||
// Set mocks base method.
|
||||
func (m *MockRedisClient) Set(arg0 context.Context, arg1 string, arg2 any, arg3 time.Duration) *redis.StatusCmd {
|
||||
func (m *MockRedisClient) Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Set", arg0, arg1, arg2, arg3)
|
||||
ret := m.ctrl.Call(m, "Set", ctx, key, value, expiration)
|
||||
ret0, _ := ret[0].(*redis.StatusCmd)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set.
|
||||
func (mr *MockRedisClientMockRecorder) Set(arg0, arg1, arg2, arg3 any) *gomock.Call {
|
||||
func (mr *MockRedisClientMockRecorder) Set(ctx, key, value, expiration any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisClient)(nil).Set), arg0, arg1, arg2, arg3)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisClient)(nil).Set), ctx, key, value, expiration)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ var defaultI18nLangNames = []string{
|
|||
"zh-CN", "简体中文",
|
||||
"zh-HK", "繁體中文(香港)",
|
||||
"zh-TW", "繁體中文(台灣)",
|
||||
"da", "Danish",
|
||||
"da", "Dansk",
|
||||
"de-DE", "Deutsch",
|
||||
"nds", "Plattdüütsch",
|
||||
"fr-FR", "Français",
|
||||
|
|
|
@ -62,7 +62,7 @@ type MarkupSanitizerRule struct {
|
|||
func loadMarkupFrom(rootCfg ConfigProvider) {
|
||||
mustMapSetting(rootCfg, "markdown", &Markdown)
|
||||
|
||||
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
|
||||
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(50000)
|
||||
FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
|
||||
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
|
||||
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/log"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// enumerates all the policy repository creating
|
||||
|
@ -26,6 +29,8 @@ var MaxUserCardsPerPage = 36
|
|||
// MaxForksPerPage sets maximum amount of forks shown per page
|
||||
var MaxForksPerPage = 40
|
||||
|
||||
var SSHInstanceKey ssh.PublicKey
|
||||
|
||||
// Repository settings
|
||||
var (
|
||||
Repository = struct {
|
||||
|
@ -109,6 +114,7 @@ var (
|
|||
SigningKey string
|
||||
SigningName string
|
||||
SigningEmail string
|
||||
Format string
|
||||
InitialCommit []string
|
||||
CRUDActions []string `ini:"CRUD_ACTIONS"`
|
||||
Merges []string
|
||||
|
@ -262,6 +268,7 @@ var (
|
|||
SigningKey string
|
||||
SigningName string
|
||||
SigningEmail string
|
||||
Format string
|
||||
InitialCommit []string
|
||||
CRUDActions []string `ini:"CRUD_ACTIONS"`
|
||||
Merges []string
|
||||
|
@ -271,6 +278,7 @@ var (
|
|||
SigningKey: "default",
|
||||
SigningName: "",
|
||||
SigningEmail: "",
|
||||
Format: "openpgp",
|
||||
InitialCommit: []string{"always"},
|
||||
CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
|
||||
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
|
||||
|
@ -376,4 +384,15 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
|
|||
log.Fatal("loadRepoArchiveFrom: %v", err)
|
||||
}
|
||||
Repository.EnableFlags = sec.Key("ENABLE_FLAGS").MustBool()
|
||||
|
||||
if Repository.Signing.Format == "ssh" && Repository.Signing.SigningKey != "none" && Repository.Signing.SigningKey != "" {
|
||||
sshPublicKey, err := os.ReadFile(Repository.Signing.SigningKey)
|
||||
if err != nil {
|
||||
log.Fatal("Could not read repository signing key in %q: %v", Repository.Signing.SigningKey, err)
|
||||
}
|
||||
SSHInstanceKey, _, _, _, err = ssh.ParseAuthorizedKey(sshPublicKey)
|
||||
if err != nil {
|
||||
log.Fatal("Could not parse the SSH signing key %q: %v", sshPublicKey, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
59
modules/setting/repository_test.go
Normal file
59
modules/setting/repository_test.go
Normal 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))
|
||||
})
|
||||
}
|
|
@ -10,3 +10,11 @@ type CreateForkOption struct {
|
|||
// name of the forked repository
|
||||
Name *string `json:"name"`
|
||||
}
|
||||
|
||||
// SyncForkInfo information about syncing a fork
|
||||
type SyncForkInfo struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
ForkCommit string `json:"fork_commit"`
|
||||
BaseCommit string `json:"base_commit"`
|
||||
CommitsBehind int `json:"commits_behind"`
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package structs
|
||||
|
||||
import "time"
|
||||
|
||||
// FileOptions options for all file APIs
|
||||
type FileOptions struct {
|
||||
// message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
|
@ -121,6 +123,8 @@ type ContentsResponse struct {
|
|||
Path string `json:"path"`
|
||||
SHA string `json:"sha"`
|
||||
LastCommitSHA string `json:"last_commit_sha"`
|
||||
// swagger:strfmt date-time
|
||||
LastCommitWhen time.Time `json:"last_commit_when"`
|
||||
// `type` will be `file`, `dir`, `symlink`, or `submodule`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package i18n
|
||||
|
@ -205,6 +206,7 @@ func (l *locale) TrString(trKey string, trArgs ...any) string {
|
|||
if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok {
|
||||
if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" {
|
||||
format = msg
|
||||
found = true
|
||||
} else if foundIndex {
|
||||
// Third fallback: old-style default language
|
||||
if msg, ok := defaultLang.idxToMsgMap[idx]; ok {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package validation
|
||||
|
@ -33,9 +32,9 @@ type Validateable interface {
|
|||
}
|
||||
|
||||
func IsValid(v Validateable) (bool, error) {
|
||||
if err := v.Validate(); len(err) > 0 {
|
||||
if valdationErrors := v.Validate(); len(valdationErrors) > 0 {
|
||||
typeof := reflect.TypeOf(v)
|
||||
errString := strings.Join(err, "\n")
|
||||
errString := strings.Join(valdationErrors, "\n")
|
||||
return false, ErrNotValid{fmt.Sprint(typeof, ": ", errString)}
|
||||
}
|
||||
|
||||
|
@ -53,6 +52,10 @@ func ValidateNotEmpty(value any, name string) []string {
|
|||
if v.IsZero() {
|
||||
isValid = false
|
||||
}
|
||||
case uint16:
|
||||
if v == 0 {
|
||||
isValid = false
|
||||
}
|
||||
case int64:
|
||||
if v == 0 {
|
||||
isValid = false
|
||||
|
|
|
@ -1414,7 +1414,7 @@ editor.file_is_a_symlink=`„%s“ je symbolický odkaz. Symbolické odkazy nemo
|
|||
editor.filename_is_a_directory=Jméno souboru „%s“ je již použito jako jméno adresáře v tomto repozitáři.
|
||||
editor.file_editing_no_longer_exists=Upravovaný soubor „%s“ již není součástí tohoto repozitáře.
|
||||
editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není součástí tohoto repozitáře.
|
||||
editor.file_changed_while_editing=Obsah souboru se od zahájení úprav změnil. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte sem</a> pro jeho zobrazení nebo <strong>odešlete změny ještě jednou</strong> pro jeho přepsání.
|
||||
editor.file_changed_while_editing=Obsah souboru se od doby, kdy jste ho otevřeli, změnil. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte sem</a> pro jeho zobrazení nebo <strong>odešlete změny ještě jednou</strong> pro jeho přepsání.
|
||||
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
|
||||
editor.commit_empty_file_header=Odeslat prázdný soubor
|
||||
editor.commit_empty_file_text=Soubor, který se chystáte odeslat, je prázdný. Pokračovat?
|
||||
|
@ -2912,6 +2912,9 @@ issues.filter_no_results = Žádné výsledky
|
|||
migrate.repo_desc_helper = Ponechte prázdné pro importování existujícího popisu
|
||||
archive.nocomment = Komentování není možné, protože je repozitář archivován.
|
||||
comment.blocked_by_user = Komentování není možné, protože jste byli zablokováni majitelem repozitáře nebo autorem.
|
||||
sync_fork.branch_behind_few = Tato větev je %d revizí pozadu za %s
|
||||
sync_fork.button = Synchronizovat
|
||||
sync_fork.branch_behind_one = Tato větev je %d revizi pozadu za %s
|
||||
|
||||
[graphs]
|
||||
component_loading_info = Tohle může chvíli trvat…
|
||||
|
@ -3930,7 +3933,7 @@ runners.delete_runner=Odstranit tento runner
|
|||
runners.delete_runner_success=Runner byl úspěšně odstraněn
|
||||
runners.delete_runner_failed=Odstranění runneru selhalo
|
||||
runners.delete_runner_header=Potvrdit odstranění tohoto runneru
|
||||
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření pracovního postupu.
|
||||
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření workflow.
|
||||
runners.status.unspecified=Neznámý
|
||||
runners.status.idle=Nečinný
|
||||
runners.status.active=Aktivní
|
||||
|
|
|
@ -2725,6 +2725,9 @@ branch.rename_branch_to = Omdøb "%s" til:
|
|||
migrate.repo_desc_helper = Lad være tom for at importere eksisterende beskrivelse
|
||||
archive.nocomment = Det er ikke muligt at kommentere, fordi depotet er arkiveret.
|
||||
comment.blocked_by_user = Det er ikke muligt at kommentere, fordi du er blokeret af depots ejer eller forfatteren.
|
||||
sync_fork.branch_behind_few = Denne gren er %d commits bag %s
|
||||
sync_fork.button = Sync
|
||||
sync_fork.branch_behind_one = Denne gren er %d commit bag %s
|
||||
|
||||
[notification]
|
||||
watching = Overvåger
|
||||
|
|
|
@ -1412,7 +1412,7 @@ editor.file_is_a_symlink=`„%s“ ist ein symbolischer Link. Symbolische Links
|
|||
editor.filename_is_a_directory=Der Dateiname „%s“ wird bereits als Verzeichnisname in diesem Repository verwendet.
|
||||
editor.file_editing_no_longer_exists=Die bearbeitete Datei „%s“ existiert nicht mehr in diesem Repository.
|
||||
editor.file_deleting_no_longer_exists=Die zu löschende Datei „%s“ existiert nicht mehr in diesem Repository.
|
||||
editor.file_changed_while_editing=Der Inhalt der Datei hat sich seit dem Beginn der Bearbeitung geändert. <a target="_blank" rel="noopener noreferrer" href="%s">Hier klicken</a>, um die Änderungen anzusehen, oder <strong>Änderungen erneut comitten</strong>, um sie zu überschreiben.
|
||||
editor.file_changed_while_editing=Der Inhalt der Datei hat sich nach dem Öffnen geändert. <a target="_blank" rel="noopener noreferrer" href="%s">Hier klicken</a>, um die Änderungen anzusehen, oder <strong>Änderungen erneut committen</strong>, um sie zu überschreiben.
|
||||
editor.file_already_exists=Eine Datei mit dem Namen „%s“ existiert bereits in diesem Repository.
|
||||
editor.commit_empty_file_header=Leere Datei committen
|
||||
editor.commit_empty_file_text=Die Datei, die du commiten willst, ist leer. Fortfahren?
|
||||
|
@ -2914,6 +2914,9 @@ issues.filter_no_results_placeholder = Versuche, deine Suchfilter anzupassen.
|
|||
migrate.repo_desc_helper = Leer lassen, um vorhandene Beschreibung zu importieren
|
||||
archive.nocomment = Kommentieren ist nicht möglich, da das Repository archiviert ist.
|
||||
comment.blocked_by_user = Kommentieren ist nicht möglich, da du vom Repository-Besitzer oder vom Autor blockiert wurdest.
|
||||
sync_fork.branch_behind_one = Dieser Branch ist %d Commit hinter %s
|
||||
sync_fork.branch_behind_few = Dieser Branch ist %d Commits hinter %s
|
||||
sync_fork.button = Sync
|
||||
|
||||
[graphs]
|
||||
component_loading_failed = Konnte %s nicht laden
|
||||
|
|
|
@ -1220,6 +1220,10 @@ archive.title_date = This repository has been archived on %s. You can view files
|
|||
archive.nocomment = Commenting is not possible because the repository is archived.
|
||||
archive.pull.noreview = This repository is archived. You cannot review pull requests.
|
||||
|
||||
sync_fork.branch_behind_one = This branch is %[1]d commit behind %[2]s
|
||||
sync_fork.branch_behind_few = This branch is %[1]d commits behind %[2]s
|
||||
sync_fork.button = Sync
|
||||
|
||||
form.reach_limit_of_creation_1 = The owner has already reached the limit of %d repository.
|
||||
form.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories.
|
||||
form.name_reserved = The repository name "%s" is reserved.
|
||||
|
@ -3814,7 +3818,7 @@ owner.settings.cleanuprules.success.update = Cleanup rule has been updated.
|
|||
owner.settings.cleanuprules.success.delete = Cleanup rule has been deleted.
|
||||
owner.settings.chef.title = Chef registry
|
||||
owner.settings.chef.keypair = Generate key pair
|
||||
owner.settings.chef.keypair.description = A key pair is necessary to authenticate to the Chef registry. If you have generated a key pair before, generating a new key pair will discard the old key pair.
|
||||
owner.settings.chef.keypair.description = Requests sent to the Chef registry must be cryptographically signed as a means of authentication. When generating a keypair, only the public key is stored on Forgejo. The private key is provided to you to be used with knife. Generating a new keypair will overwrite the previous one.
|
||||
|
||||
[secrets]
|
||||
secrets = Secrets
|
||||
|
@ -3871,7 +3875,7 @@ runners.delete_runner = Delete this runner
|
|||
runners.delete_runner_success = Runner deleted successfully
|
||||
runners.delete_runner_failed = Failed to delete runner
|
||||
runners.delete_runner_header = Confirm to delete this runner
|
||||
runners.delete_runner_notice = If a task is running on this runner, it will be terminated and mark as failed. It may break building workflow.
|
||||
runners.delete_runner_notice = If a task is running on this runner, it will be terminated and marked as failed. It may break building workflow.
|
||||
runners.none = No runners available
|
||||
runners.status.unspecified = Unknown
|
||||
runners.status.idle = Idle
|
||||
|
|
|
@ -38,12 +38,12 @@ passcode=Código de acceso
|
|||
|
||||
webauthn_insert_key=Introduzca su clave de seguridad
|
||||
webauthn_sign_in=Presione el botón en su clave de seguridad. Si su clave de seguridad no tiene ningún botón, vuelva a insertarla.
|
||||
webauthn_press_button=Por favor, presione el botón de su llave de seguridad…
|
||||
webauthn_press_button=Por favor, presione el botón en su clave de seguridad…
|
||||
webauthn_use_twofa=Utilice un código de doble factor desde su teléfono móvil
|
||||
webauthn_error=No se pudo leer su llave de seguridad.
|
||||
webauthn_unsupported_browser=Su navegador no soporta actualmente WebAuthn.
|
||||
webauthn_error_unknown=Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo.
|
||||
webauthn_error_insecure=`WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1"`
|
||||
webauthn_error_insecure=WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1"
|
||||
webauthn_error_unable_to_process=El servidor no pudo procesar su solicitud.
|
||||
webauthn_error_duplicated=La clave de seguridad no está permitida para esta solicitud. Por favor, asegúrese de que la clave no está ya registrada.
|
||||
webauthn_error_empty=Debe establecer un nombre para esta clave.
|
||||
|
@ -72,7 +72,7 @@ all=Todos
|
|||
sources=Propios
|
||||
mirrors=Réplica
|
||||
collaborative=Colaborativo
|
||||
forks=Forks
|
||||
forks=Bifurcaciones
|
||||
|
||||
activities=Actividades
|
||||
pull_requests=Solicitudes de incorporación de cambios
|
||||
|
@ -300,7 +300,7 @@ offline_mode.description=Deshabilitar redes de distribución de contenido de ter
|
|||
disable_gravatar=Desactivar Gravatar
|
||||
disable_gravatar.description=Desactivar el Gravatar y otros fuentes de avatares de terceros. Se utilizará un avatar por defecto a menos que un usuario suba un avatar localmente.
|
||||
federated_avatar_lookup=Habilitar avatares federados
|
||||
federated_avatar_lookup.description=Buscar de avatares con Libravatar.
|
||||
federated_avatar_lookup.description=Busca avatares con Libravatar.
|
||||
disable_registration=Deshabilitar auto-registro
|
||||
disable_registration.description=Sólo los administradores de la instancia podrán crear nuevas cuentas. Es muy recomendable mantener deshabilitado el registro a menos que pretenda alojar una instancia pública para todo el mundo y esté preparado para lidiar con grandes cantidades de cuentas de spam.
|
||||
allow_only_external_registration.description=Los usuarios sólo podrán crear nuevas cuentas utilizando servicios externos configurados.
|
||||
|
@ -668,7 +668,7 @@ still_own_packages=Tu cuenta posee uno o más paquetes, elimínalos primero.
|
|||
org_still_own_repo=Esta organización todavía posee uno o más repositorios, elimínalos o transfiérelos primero.
|
||||
org_still_own_packages=Esta organización todavía posee uno o más paquetes, elimínalos primero.
|
||||
|
||||
target_branch_not_exist=La rama de destino no existe
|
||||
target_branch_not_exist=La rama de destino no existe.
|
||||
admin_cannot_delete_self = No puedes eliminarte a ti mismo cuando eres un admin (administrador). Por favor, elimina primero tus privilegios de administrador.
|
||||
username_error_no_dots = ` solo puede contener carácteres alfanuméricos ("0-9","a-z","A-Z"), guiones ("-"), y guiones bajos ("_"). No puede empezar o terminar con carácteres no alfanuméricos y también están prohibidos los carácteres no alfanuméricos consecutivos.`
|
||||
unsupported_login_type = No se admite el tipo de inicio de sesión para eliminar la cuenta.
|
||||
|
@ -843,7 +843,7 @@ add_email_success=La nueva dirección de correo electrónico ha sido añadida.
|
|||
email_preference_set_success=La preferencia de correo electrónico se ha establecido correctamente.
|
||||
add_openid_success=La nueva dirección OpenID ha sido añadida.
|
||||
keep_email_private=Ocultar dirección de correo electrónico
|
||||
keep_email_private_popup=Esto ocultará tu dirección de correo electrónico de tu perfil. Ya no será la dirección predeterminada para los confirmaciones realizadas a través de la interfaz web, como las subidas y ediciones de archivos, y no se utilizará para las confirmaciones de fusión. En su lugar, se utilizará una dirección especial %s para asociar las confirmaciones a tu cuenta. Ten en cuenta que cambiar esta opción no afectará a las confirmaciones existentes.
|
||||
keep_email_private_popup=Su dirección de correo electrónico no se mostrará en su perfil y no será la predeterminada para las confirmaciones realizadas a través de la interfaz web, como las subidas de archivos, las ediciones y las confirmaciones de fusión. En su lugar, se utilizará una dirección especial %s para vincular las confirmaciones a tu cuenta. Esta opción no afectará a las confirmaciones existentes.
|
||||
openid_desc=OpenID le permite delegar la autenticación a un proveedor externo.
|
||||
|
||||
manage_ssh_keys=Gestionar claves SSH
|
||||
|
@ -1075,6 +1075,14 @@ keep_pronouns_private = Mostrar pronombres solo a personas autenticadas
|
|||
storage_overview = Resumen del almacenamiento
|
||||
quota.sizes.assets.artifacts = Artefactos
|
||||
quota.sizes.assets.attachments.releases = Archivos adjuntos del lanzamiento
|
||||
change_username_redirect_prompt.with_cooldown.few = El antiguo nombre de usuario estará disponible para todos después un periodo de tiempo de espera de %[1]d días, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
|
||||
change_username_redirect_prompt.with_cooldown.one = El antiguo nombre de usuario estará disponible para todos después un periodo de tiempo de espera de %[1]d día, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
|
||||
quota.rule.exceeded = Excedido
|
||||
quota.rule.no_limit = Ilimitado
|
||||
quota.sizes.assets.all = Activos
|
||||
quota.sizes.git.lfs = Git LFS
|
||||
quota.sizes.assets.attachments.issues = Archivos adjuntos de incidencia
|
||||
access_token_regeneration = Regenerar token de acceso
|
||||
|
||||
[repo]
|
||||
owner=Propietario
|
||||
|
@ -1237,7 +1245,7 @@ migrate.migrate_items_options=Se necesita un token de acceso para migrar element
|
|||
migrated_from=Migrado desde <a href="%[1]s">%[2]s</a>
|
||||
migrated_from_fake=Migrado desde %[1]s
|
||||
migrate.migrate=Migrar desde %s
|
||||
migrate.migrating=Migrando desde <b>%s</b>...
|
||||
migrate.migrating=Migrando desde <b>%s</b>…
|
||||
migrate.migrating_failed=La migración desde <b>%s</b> ha fallado.
|
||||
migrate.migrating_failed.error=Error al migrar: %s
|
||||
migrate.migrating_failed_no_addr=Migración fallida.
|
||||
|
@ -1510,7 +1518,7 @@ issues.new.no_projects=Ningún proyecto
|
|||
issues.new.open_projects=Proyectos abiertos
|
||||
issues.new.closed_projects=Proyectos cerrados
|
||||
issues.new.no_items=No hay elementos
|
||||
issues.new.milestone=Milestone
|
||||
issues.new.milestone=Hito
|
||||
issues.new.no_milestone=Sin hito
|
||||
issues.new.clear_milestone=Limpiar Milestone
|
||||
issues.new.open_milestone=Hitos abiertos
|
||||
|
@ -1558,12 +1566,12 @@ issues.change_title_at=`cambió el título de <b><strike>%s</strike></b> a <b>%s
|
|||
issues.change_ref_at=`cambió referencia de <b><strike>%s</strike></b> a <b>%s</b> %s`
|
||||
issues.remove_ref_at=`eliminó la referencia <b>%s</b> %s`
|
||||
issues.add_ref_at=`añadió la referencia <b>%s</b> %s`
|
||||
issues.delete_branch_at=`rama eliminada <b>%s</b> %s`
|
||||
issues.delete_branch_at=`eliminó la rama <b>%s</b> %s`
|
||||
issues.filter_label=Etiqueta
|
||||
issues.filter_label_exclude=`Usa <code>alt</code> + <code>clic/enter</code> para excluir etiquetas`
|
||||
issues.filter_label_no_select=Todas las etiquetas
|
||||
issues.filter_label_select_no_label=Sin etiqueta
|
||||
issues.filter_milestone=Milestone
|
||||
issues.filter_milestone=Hito
|
||||
issues.filter_milestone_all=Todos los hitos
|
||||
issues.filter_milestone_none=Sin hitos
|
||||
issues.filter_milestone_open=Abrir hitos
|
||||
|
@ -1965,7 +1973,7 @@ pulls.auto_merge_canceled_schedule_comment=`canceló la fusión automática de e
|
|||
pulls.delete.title=¿Borrar este pull request?
|
||||
pulls.delete.text=¿Realmente quieres eliminar esta pull request? (Esto eliminará permanentemente todo el contenido. Considera cerrarlo si simplemente deseas archivarlo)
|
||||
|
||||
pulls.recently_pushed_new_branches=Has realizado push en la rama <strong>%[1]s</strong> %[2]s
|
||||
pulls.recently_pushed_new_branches=Empujaste en la rama <a href="%[3]s"><strong>%[1]s</strong></a> %[2]s
|
||||
|
||||
pull.deleted_branch=(eliminado):%s
|
||||
|
||||
|
@ -1976,7 +1984,7 @@ milestones.no_due_date=Sin fecha límite
|
|||
milestones.open=Abrir
|
||||
milestones.close=Cerrar
|
||||
milestones.new_subheader=Los hitos pueden ayudarle a organizar los problemas y monitorizar su progreso.
|
||||
milestones.completeness=%d%% Completado
|
||||
milestones.completeness=<strong>%d%%</strong> Completado
|
||||
milestones.create=Crear hito
|
||||
milestones.title=Título
|
||||
milestones.desc=Descripción
|
||||
|
@ -2017,7 +2025,7 @@ ext_wiki=Wiki externa
|
|||
ext_wiki.desc=Enlace a una wiki externa.
|
||||
|
||||
wiki=Wiki
|
||||
wiki.welcome=¡Bienvenidos a la Wiki!
|
||||
wiki.welcome=Bienvenido a la Wiki.
|
||||
wiki.welcome_desc=Esta wiki le permite escribir y compartir documentación con otros colaboradores.
|
||||
wiki.desc=Escriba y comparta documentación con colaboradores.
|
||||
wiki.create_first_page=Crear la primera página
|
||||
|
@ -2326,7 +2334,7 @@ settings.event_create=Crear
|
|||
settings.event_create_desc=Rama o etiqueta creada.
|
||||
settings.event_delete=Eliminar
|
||||
settings.event_delete_desc=Rama o etiqueta eliminada.
|
||||
settings.event_fork=Fork
|
||||
settings.event_fork=Bifurcación
|
||||
settings.event_fork_desc=Repositorio forkeado.
|
||||
settings.event_wiki=Wiki
|
||||
settings.event_wiki_desc=Página de la Wiki creada, renombrada, editada o eliminada.
|
||||
|
@ -2514,7 +2522,7 @@ settings.archive.branchsettings_unavailable=Los ajustes de rama no están dispon
|
|||
settings.archive.tagsettings_unavailable=Los ajustes de las etiquetas no están disponibles si el repositorio está archivado.
|
||||
settings.unarchive.button=Desarchivar repositorio
|
||||
settings.unarchive.header=Desarchivar este repositorio
|
||||
settings.unarchive.text=La desarchivación del repositorio restablecerá su capacidad de recibir confirmaciones y subidos, así como nuevas incidencias y solicitudes de incorporación de cambios.
|
||||
settings.unarchive.text=La desarchivación del repositorio restablecerá su capacidad de recibir confirmaciones y empujes, así como nuevas incidencias y solicitudes de incorporación de cambios.
|
||||
settings.unarchive.success=El repositorio se ha desarchivado correctamente.
|
||||
settings.unarchive.error=Ocurrió un error mientras se trataba de des-archivar el repositorio. Revisa el registro para más detalles.
|
||||
settings.update_avatar_success=El avatar del repositorio ha sido actualizado.
|
||||
|
@ -2532,7 +2540,7 @@ settings.lfs_invalid_locking_path=Ruta no válida: %s
|
|||
settings.lfs_invalid_lock_directory=No se puede bloquear el directorio: %s
|
||||
settings.lfs_lock_already_exists=El bloqueo ya existe: %s
|
||||
settings.lfs_lock=Bloquear
|
||||
settings.lfs_lock_path=Ruta del archivo a bloquear...
|
||||
settings.lfs_lock_path=Ruta del archivo a bloquear…
|
||||
settings.lfs_locks_no_locks=Sin bloqueos
|
||||
settings.lfs_lock_file_no_exist=El archivo bloqueado no existe en la rama por defecto
|
||||
settings.lfs_force_unlock=Forzar desbloqueo
|
||||
|
@ -2637,7 +2645,7 @@ release.cancel=Cancelar
|
|||
release.publish=Publicar lanzamiento
|
||||
release.save_draft=Guardar borrador
|
||||
release.edit_release=Actualizar Lanzamiento
|
||||
release.delete_release=Eliminar Lanzamiento
|
||||
release.delete_release=Eliminar lanzamiento
|
||||
release.delete_tag=Eliminar tag
|
||||
release.deletion=Eliminar lanzamiento
|
||||
release.deletion_desc=Eliminar un lanzamiento sólo lo elimina de Forgejo. No afectará la etiqueta Git, el contenido de su repositorio o su historial. ¿Continuar?
|
||||
|
@ -2862,9 +2870,15 @@ release.add_external_asset = Añadir un recurso externo
|
|||
settings.enforce_on_admins_desc = Los administradores del repositorio no pueden saltarse esta regla.
|
||||
pulls.editable = Editable
|
||||
issues.filter_no_results = No hay resultados
|
||||
release.type_attachment = Archivo adjunto
|
||||
sync_fork.button = Sincronizar
|
||||
settings.sourcehut_builds.visibility = Visibilidad de trabajo
|
||||
settings.ignore_stale_approvals = Ignorar las aprobaciones obsoletas
|
||||
settings.event_pull_request_enforcement = Aplicación
|
||||
issues.reaction.alt_few = %[1]s reaccionado con %[2]s.
|
||||
|
||||
[graphs]
|
||||
component_loading = Cargando %s...
|
||||
component_loading = Cargando %s…
|
||||
component_loading_failed = No se pudo cargar %s
|
||||
contributors.what = contribuciones
|
||||
recent_commits.what = commits recientes
|
||||
|
@ -2929,11 +2943,11 @@ settings.hooks_desc=Añadir webhooks que serán ejecutados para <strong>todos lo
|
|||
|
||||
settings.labels_desc=Añadir etiquetas que pueden ser utilizadas en problemas para <strong>todos los repositorios</strong> bajo esta organización.
|
||||
|
||||
members.membership_visibility=Visibilidad de Membresía:
|
||||
members.membership_visibility=Visibilidad de membresía:
|
||||
members.public=Público
|
||||
members.public_helper=hacer oculto
|
||||
members.public_helper=Hacer oculto
|
||||
members.private=Oculto
|
||||
members.private_helper=hacer público
|
||||
members.private_helper=Hacer público
|
||||
members.member_role=Rol del miembro:
|
||||
members.owner=Propietario
|
||||
members.member=Miembro
|
||||
|
@ -2942,7 +2956,7 @@ members.remove.detail=¿Destituir a %[1]s de %[2]s?
|
|||
members.leave=Abandonar
|
||||
members.leave.detail=¿Irse de %s?
|
||||
members.invite_desc=Añadir un miembro nuevo a %s:
|
||||
members.invite_now=Invitar
|
||||
members.invite_now=Invitar ahora
|
||||
|
||||
teams.join=Unirse
|
||||
teams.leave=Abandonar
|
||||
|
@ -2951,7 +2965,7 @@ teams.can_create_org_repo=Crear repositorios
|
|||
teams.can_create_org_repo_helper=Los miembros pueden crear nuevos repositorios en la organización. El creador obtendrá acceso al administrador del nuevo repositorio.
|
||||
teams.none_access=Sin acceso
|
||||
teams.none_access_helper=Los miembros no pueden ver o hacer ninguna otra acción en esta unidad.
|
||||
teams.general_access=Acceso general
|
||||
teams.general_access=Acceso personalizado
|
||||
teams.general_access_helper=Los permisos de los miembros se decidirán por debajo de la tabla de permisos.
|
||||
teams.read_access=Leer
|
||||
teams.read_access_helper=Los miembros pueden ver y clonar los repositorios del equipo.
|
||||
|
@ -2997,7 +3011,7 @@ teams.invite.by=Invitado por %s
|
|||
teams.invite.description=Por favor, haga clic en el botón de abajo para unirse al equipo.
|
||||
follow_blocked_user = No puedes seguir a esta organización porque esta organización te ha bloqueado.
|
||||
open_dashboard = Abrir panel de control
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.few = El antiguo nombre de usuario estará disponible para cualquiera después un periodo de tiempo de espera de %[1]d días, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.few = El antiguo nombre de la organización estará disponible para todos después un periodo de tiempo de espera de %[1]d días, aún puedes reclamar el antiguo nombre durante el periodo de tiempo de espera.
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.one = El antiguo nombre de usuario estará disponible para cualquiera después un periodo de tiempo de espera de %[1]d día, aún puedes reclamar el antiguo nombre de usuario durante el periodo de tiempo de espera.
|
||||
|
||||
[admin]
|
||||
|
@ -3539,6 +3553,9 @@ emails.delete_primary_email_error = No puedes eliminar el correo electrónico pr
|
|||
config.cache_test =Caché de prueba
|
||||
emails.delete_desc = ¿Estás seguro que quieres eliminar esta dirección de correo electrónico?
|
||||
monitor.duration = Duración (es)
|
||||
self_check = Autocomprobación
|
||||
config.app_slogan = Eslogan de la instancia
|
||||
dashboard.sync_tag.started = Sincronización de etiquetas iniciada
|
||||
|
||||
|
||||
[action]
|
||||
|
@ -3799,6 +3816,12 @@ alt.install = Instalar paquete
|
|||
alt.repository = Información del repositorio
|
||||
alt.repository.architectures = Arquitecturas
|
||||
alt.repository.multiple_groups = Este paquete está disponible en múltiples grupos.
|
||||
arch.version.description = Descripción
|
||||
arch.version.provides = Proveedores
|
||||
npm.dependencies.bundle = Empaquetar dependencias
|
||||
arch.version.checkdepends = Comprobar dependencias
|
||||
arch.version.optdepends = Dependencias opcionales
|
||||
arch.version.makedepends = Construir dependencias
|
||||
|
||||
[secrets]
|
||||
secrets=Secretos
|
||||
|
@ -3904,6 +3927,8 @@ variables.id_not_exist = Variable con id %d no existe.
|
|||
runs.empty_commit_message = (mensaje de commit vacío)
|
||||
runs.expire_log_message = Los registros han sido eliminados porque eran demasiado antiguos.
|
||||
runs.workflow = Flujo de trabajo
|
||||
workflow.dispatch.run = Correr flujo de trabajo
|
||||
workflow.dispatch.use_from = Usar el flujo de trabajo de
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Proyecto individual
|
||||
|
@ -3946,7 +3971,7 @@ exact = Exacto
|
|||
exact_tooltip = Incluir sólo los resultados que corresponden al término de búsqueda exacto
|
||||
issue_kind = Buscar incidencias…
|
||||
fuzzy = Difusa
|
||||
runner_kind = Buscar ejecutores…
|
||||
runner_kind = Buscar corredores…
|
||||
regexp_tooltip = Interpretar los términos de búsqueda como una expresión regular
|
||||
regexp = Expresión Regular
|
||||
|
||||
|
|
1
options/locale/locale_eu.ini
Normal file
1
options/locale/locale_eu.ini
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -545,6 +545,7 @@ issue_assigned.issue = @%[1]s osoitti sinulle ongelman %[2]s repossa %[3]s.
|
|||
register_notify.text_1 = tämä on %s:n rekistöröitymisen vahvistussähköposti!
|
||||
reset_password.text = jos tämä oli sinun toimestasi, ole hyvä ja klikkaa oheista linkkiä palauttaaksesi tilisi <b>%s</b> sisällä:
|
||||
totp_disabled.no_2fa = Muita kaksivaiheisen tunnistautumisen menetelmiä ei ole konfiguroituna, joten et tarvitse kaksivaiheista tunnistautumista kirjautuaaksesi tilillesi.
|
||||
totp_enrolled.subject = Olet aktivoinut TOTP:in kaksivaiheisen todennuksen menetelmäksi
|
||||
|
||||
|
||||
|
||||
|
@ -666,6 +667,8 @@ follow_blocked_user = Et voi seurata tätä käyttäjää, koska olet estänyt k
|
|||
disabled_public_activity = Käyttäjä on poistanut käytöstä toiminnan julkisen näkyvyyden.
|
||||
form.name_reserved = Käyttäjätunnus "%s" on varattu.
|
||||
form.name_pattern_not_allowed = Kaava "%s" ei ole sallittu käyttäjätunnuksessa.
|
||||
public_activity.visibility_hint.admin_private = Aktiivisuus on näkyvissä sinulle, koska olet ylläpitäjä, mutta käyttäjä haluaa pitää aktiivisuutensa yksityisenä.
|
||||
public_activity.visibility_hint.self_private_profile = Aktiivisuutesi on näkyvissä vain sinulle ja instanssin ylläpitäjille, koska profiilisi on yksityinen. <a href="%s">Määritä</a>.
|
||||
|
||||
|
||||
[settings]
|
||||
|
@ -994,12 +997,12 @@ download_tar=Lataa TAR.GZ
|
|||
repo_desc=Kuvaus
|
||||
repo_lang=Kieli
|
||||
repo_gitignore_helper=Valitse .gitignore-mallit
|
||||
issue_labels=Ongelmien tunnisteet
|
||||
issue_labels=Tunnisteet
|
||||
issue_labels_helper=Valitse nimiöjoukko
|
||||
license=Lisenssi
|
||||
license_helper=Valitse lisenssitiedosto
|
||||
readme=README
|
||||
auto_init=Alusta repo (Luo .gitignore, License ja README)
|
||||
auto_init=Alusta repo
|
||||
create_repo=Luo repo
|
||||
default_branch=Oletushaara
|
||||
mirror_prune=Karsi
|
||||
|
@ -1168,7 +1171,7 @@ issues.new_label=Uusi tunniste
|
|||
issues.new_label_placeholder=Tunnisteen nimi
|
||||
issues.new_label_desc_placeholder=Kuvaus
|
||||
issues.create_label=Luo tunniste
|
||||
issues.label_templates.helper=Valitse tunnistejoukko
|
||||
issues.label_templates.helper=Valitse tunnisteen esiasetus
|
||||
issues.add_milestone_at=`lisäsi tämän merkkipaaluun <b>%s</b> %s`
|
||||
issues.change_milestone_at=`vaihtoi merkkipaalun <b>%s</b> merkkipaaluun <b>%s</b> %s`
|
||||
issues.remove_milestone_at=`poisti tämän <b>%s</b> merkkipaalusta %s`
|
||||
|
@ -1244,7 +1247,7 @@ issues.label_count=%d tunnistetta
|
|||
issues.label_open_issues=%d avointa ongelmaa
|
||||
issues.label_edit=Muokkaa
|
||||
issues.label_delete=Poista
|
||||
issues.label_modify=Muokkaa tunniste
|
||||
issues.label_modify=Muokkaa tunnistetta
|
||||
issues.label_deletion=Poista tunniste
|
||||
issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä
|
||||
issues.label.filter_sort.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
|
||||
|
@ -1272,7 +1275,7 @@ issues.start_tracking_history=`aloitti työskentelyn %s`
|
|||
issues.tracker_auto_close=Ajan seuranta pysähtyy automaattisesti kun tämä ongelma on suljettu
|
||||
issues.stop_tracking=Pysäytä ajanotto
|
||||
issues.stop_tracking_history=`lopetti työskentelyn %s`
|
||||
issues.add_time=Lisää aika käsin
|
||||
issues.add_time=Lisää aika manuaalisesti
|
||||
issues.add_time_short=Lisää aika
|
||||
issues.add_time_cancel=Peruuta
|
||||
issues.add_time_history=`lisäsi käytetyn ajan %s`
|
||||
|
@ -2129,13 +2132,62 @@ archive.pull.noreview = Tämä repo on arkistoitu. Et voi katselmoida vetopyynt
|
|||
tree_path_not_found_tag = Polkua %[1]s ei ole olemassa tagissa %[2]s
|
||||
transfer.no_permission_to_accept = Sinulla ei ole oikeutta hyväksyä tätä siirtoa.
|
||||
settings.web_hook_name_feishu = Feishu / Lark Suite
|
||||
issues.review.reviewers = Katselmoijat
|
||||
issues.new.no_reviewers = Ei katselmoijia
|
||||
issues.add_label = lisäsi tunnisteen %s %s
|
||||
issues.due_date_added = lisäsi määräpäivän %s %s
|
||||
issues.review.add_review_request = pyysi katselmointia käyttäjältä %[1]s %[2]s
|
||||
issues.ref_pull_from = `<a href="%[3]s">viittasi tähän vetopyyntöön %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.commit_ref_at = `viittasi tähän vetopyyntöön kommitista <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.review.comment = katselmoi %s
|
||||
issues.add_labels = lisäsi tunnisteet %s %s
|
||||
issues.review.add_review_requests = pyysi katselmointeja käyttäjiltä %[1]s %[2]s
|
||||
pulls.blocked_by_official_review_requests = Tämä vetopyyntö on estetty, koska siltä puuttuu hyväksyntä yhdeltä tai useammalta viralliselta katselmoijalta.
|
||||
issues.author.tooltip.issue = Tämä käyttäjä on tämän ongelman tekijä.
|
||||
issues.author.tooltip.pr = Tämä käyttäjä on tämän vetopyynnön tekijä.
|
||||
issues.role.contributor_helper = Tämä käyttäjä on aiemmin kommitoinut tähän repoon.
|
||||
settings.event_pull_request_label = Tunnisteet
|
||||
issues.due_date_remove = poisti määräpäivän %s %s
|
||||
settings.event_issue_label = Tunnisteet
|
||||
settings.authorization_header = Authorization-otsake
|
||||
diff.has_escaped = Tällä rivillä on piilotettuja Unicode-merkkejä
|
||||
issues.max_pinned = Et voi kiinnittää enempää ongelmia
|
||||
settings.external_tracker_url_error = Ulkoisen ongelmienseurannan URL-osoite ei ole kelvollinen.
|
||||
settings.event_pull_request_review = Katselmoinnit
|
||||
settings.event_pull_request_review_request = Katselmointipyynnöt
|
||||
issues.num_reviews_one = %d katselmointi
|
||||
issues.num_reviews_few = %d katselmointia
|
||||
issues.filter_no_results = Ei tuloksia
|
||||
issues.filter_no_results_placeholder = Kokeile määrittää eri hakusuodattimet.
|
||||
projects.edit_subheader = Projektit organisoivat ongelmia ja seuraavat edistymistä.
|
||||
issues.label_templates.title = Lataa tunnisteen esiasetus
|
||||
issues.label_deletion_desc = Tunnisteen poistaminen poistaa sen kaikista ongelmista. Jatketaanko?
|
||||
issues.attachment.download = `Napsauta ladataksesi "%s"`
|
||||
issues.review.option.hide_outdated_comments = Piilota vanhentuneet kommentit
|
||||
issues.review.show_outdated = Näytä vanhentuneet
|
||||
issues.review.hide_outdated = Piilota vanhentuneet
|
||||
settings.external_wiki_url_error = Ulkoisen wikin URL-osoite ei ole kelvollinen.
|
||||
issues.attachment.open_tab = `Napsauta nähdäksesi "%s" uudessa välilehdessä`
|
||||
issues.unpin_issue = Poista ongelman kiinnitys
|
||||
issues.review.outdated_description = Sisältö on muuttunut siitä ajanhetkestä, kun tämä kommentti luotiin
|
||||
issues.review.option.show_outdated_comments = Näytä vanhentuneet kommentit
|
||||
issues.review.outdated = Vanhentunut
|
||||
issues.label_templates.use = Käytä tunnisteen esiasetusta
|
||||
issues.label_deletion_success = Tunniste on poistettu.
|
||||
issues.cancel_tracking = Hylkää
|
||||
issues.choose.get_started = Aloitetaan
|
||||
settings.event_fork = Forkkaus
|
||||
pulls.no_merge_access = Sinulla ei ole valtuutta yhdistää tätä vetopyyntöä.
|
||||
pulls.sign_in_require = <a href="%s">Kirjaudu sisään</a> luodaksesi uuden vetopyynnön.
|
||||
delete_preexisting = Poista olemassa olevat tiedostot
|
||||
issues.reaction.add = Lisää reaktio
|
||||
|
||||
|
||||
|
||||
[graphs]
|
||||
component_loading_info = Tämä saattaa kestää hetken…
|
||||
component_failed_to_load = Odottamaton virhe.
|
||||
component_loading = Ladataan %s...
|
||||
component_loading = Ladataan %s…
|
||||
contributors.what = kontribuutiot
|
||||
recent_commits.what = viimeisimmät kommitit
|
||||
code_frequency.what = koodifrekvenssi
|
||||
|
@ -2319,7 +2371,7 @@ users.password_helper=Jätä salasanakenttä tyhjäksi jos haluat pitää sen mu
|
|||
users.update_profile_success=Käyttäjän tili on päivitetty.
|
||||
users.edit_account=Muokkaa käyttäjätiliä
|
||||
users.max_repo_creation_desc=(Aseta -1 käyttääksesi globaalia oletusrajaa.)
|
||||
users.is_activated=Käyttäjätili on aktivoitu
|
||||
users.is_activated=Aktivoitu tili
|
||||
users.prohibit_login=Ota sisäänkirjautuminen pois käytöstä
|
||||
users.is_admin=Ylläpitäjätili
|
||||
users.is_restricted=Rajoitettu tili
|
||||
|
@ -2406,7 +2458,7 @@ auths.edit=Muokkaa todennuslähdettä
|
|||
auths.update_success=Todennuslähde on päivitetty.
|
||||
auths.update=Päivitä todennuslähde
|
||||
auths.delete=Poista todennuslähde
|
||||
auths.delete_auth_title=Todennuslähteen poisto
|
||||
auths.delete_auth_title=Poista todennuslähde
|
||||
auths.delete_auth_desc=Todennuslähteen poisto estää käyttäjiä käyttämästä sitä kirjautumiseen. Jatketaanko?
|
||||
auths.deletion_success=Todennuslähde on poistettu.
|
||||
|
||||
|
@ -2445,7 +2497,7 @@ config.show_registration_button=Näytä rekisteröitymispainike
|
|||
config.enable_captcha=Ota CAPTCHA käyttöön
|
||||
config.active_code_lives=Aktivointikoodin vanhenemisaika
|
||||
config.default_keep_email_private=Piilota sähköpostiosoitteet oletuksena
|
||||
config.default_visibility_organization=Uuden organisaation oletusnäkyvyys
|
||||
config.default_visibility_organization=Uusien organisaatioiden oletusnäkyvyys
|
||||
|
||||
config.webhook_config=Webkoukkujen asetukset
|
||||
config.queue_length=Jonon pituus
|
||||
|
@ -2468,7 +2520,7 @@ config.cache_conn=Välimuistin yhteys merkkijono
|
|||
config.session_config=Istunnon asetukset
|
||||
config.session_provider=Istunnon toimittaja
|
||||
config.provider_config=Toimittajan asetukset
|
||||
config.cookie_name=Evästenimi
|
||||
config.cookie_name=Evästeen nimi
|
||||
config.gc_interval_time=GC aikaväli aika
|
||||
config.session_life_time=Istunnon elinikä
|
||||
config.https_only=Vain HTTPS
|
||||
|
@ -2602,6 +2654,12 @@ config.default_allow_only_contributors_to_track_time = Salli vain avustajien seu
|
|||
monitor.download_diagnosis_report = Lataa diagnostiikkaraportti
|
||||
monitor.duration = Kesto (s)
|
||||
monitor.last_execution_result = Tulos
|
||||
users.bot = Botti
|
||||
auths.syncenabled = Käytä käyttäjäsynkronointia
|
||||
auths.enable_ldap_groups = Käytä LDAP-ryhmiä
|
||||
dashboard.sync_branch.started = Haarasynkronointi aloitettu
|
||||
dashboard.sync_tag.started = Tagisynkronointi aloitettu
|
||||
auths.login_source_exist = Todennuslähde "%s" on jo olemassa.
|
||||
|
||||
|
||||
[action]
|
||||
|
|
|
@ -1536,7 +1536,7 @@ pulls.merged_by =ni/ng <a href="%[2]s">%[3]s</a> ay naisama %[1]s
|
|||
commitstatus.pending = Nakabinbin
|
||||
issues.review.pending = Nakabinbin
|
||||
pulls.status_checking = Nakabinbin ang ilang mga pagsusuri
|
||||
editor.file_changed_while_editing = Ang nilalaman ng file ay nagbago mula noong nagsimula kang mag-edit. <a target="_blank" rel="noopener noreferrer" href="%s">Mag-click dito</a> upang makita ang mga pagbabago o <strong>Mag-commit ng mga pagbabago muli</strong> para i-overwrite sila.
|
||||
editor.file_changed_while_editing = Ang nilalaman ng file ay nagbago mula noong binuksan mo ang file. <a target="_blank" rel="noopener noreferrer" href="%s">Mag-click dito</a> upang makita ang mga pagbabago o <strong>Mag-commit ng mga pagbabago muli</strong> para i-overwrite sila.
|
||||
editor.file_already_exists = Umiiral na ang file na may pangalang "%s" sa repositoryong ito.
|
||||
issues.review.review = Suriin
|
||||
activity.git_stats_push_to_branch = sa %s at
|
||||
|
@ -2770,6 +2770,9 @@ issues.filter_no_results_placeholder = Subukang ayusin ang iyong mga filter sa p
|
|||
migrate.repo_desc_helper = Iwanang walang laman para i-import ang umiiral na paglalarawan
|
||||
archive.nocomment = Hindi posible ang pagkomento dahil naka-archive ang repositoryo.
|
||||
comment.blocked_by_user = Hindi posible ang pagkomento dahil hinarang ka ng may-ari ng repositoryo o ng may-akda.
|
||||
sync_fork.button = I-sync
|
||||
sync_fork.branch_behind_one = Ang branch na ito ay %d commit sa likod ng %s
|
||||
sync_fork.branch_behind_few = Ang branch na ito ay %d mga commit sa likod ng %s
|
||||
|
||||
[search]
|
||||
commit_kind = Maghanap ng mga commit…
|
||||
|
|
|
@ -1064,6 +1064,32 @@ user_block_yourself = Vous ne pouvez pas vous bloquer vous même.
|
|||
pronouns_custom_label = Pronoms personnalisés
|
||||
change_username_redirect_prompt.with_cooldown.one = L'ancien pseudonyme sera disponible pour n'importe qui après une période d'%[1]d jour, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
|
||||
change_username_redirect_prompt.with_cooldown.few = L'ancien pseudonyme sera disponible pour n'importe qui après une période de %[1]d jours, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
|
||||
quota.rule.exceeded = Dépassé
|
||||
regenerate_token = Régénérer
|
||||
access_token_regeneration = Régénérer le token d'accès
|
||||
access_token_regeneration_desc = La régénération d'un token révoquera l'accès à votre compte pour les applications qui l'utilisaient. Cela n'est pas reversible. Continuer ?
|
||||
regenerate_token_success = Le token a été régénéré. Les applications qui l'utilisent n'ont plus accès à votre compte et doivent être mises à jour avec le nouveau token.
|
||||
quota.applies_to_org = Les quotas suivants s'applique à cette organisation
|
||||
quota.rule.no_limit = Sans limite
|
||||
quota.sizes.all = Tout
|
||||
quota.sizes.repos.all = Dépôts
|
||||
quota.sizes.repos.public = Dépôts publics
|
||||
quota.sizes.repos.private = Dépôts privés
|
||||
quota.sizes.git.all = Contenu dans Git
|
||||
quota.sizes.git.lfs = Git LFS
|
||||
quota.sizes.assets.all = Contenus
|
||||
quota.sizes.assets.attachments.all = Attachements
|
||||
quota.sizes.assets.attachments.issues = Attachements de tickets
|
||||
quota.sizes.assets.attachments.releases = Attachements de version
|
||||
quota.sizes.assets.artifacts = Artefacts
|
||||
quota.sizes.assets.packages.all = Paquets
|
||||
quota.sizes.wiki = Wiki
|
||||
quota.applies_to_user = Les quotas suivants s'appliquent à votre compte
|
||||
quota.rule.exceeded.helper = La taille totale des objets pour cette règle ont dépassé le quota.
|
||||
keep_pronouns_private = Ne montrer les pronoms qu'aux utilisateurs authentifiés
|
||||
keep_pronouns_private.description = Cela masquera votre pronoms aux visiteurs qui ne sont pas authentifiés.
|
||||
storage_overview = Vue d'ensemble du stockage
|
||||
quota = Quota
|
||||
|
||||
[repo]
|
||||
new_repo_helper=Un dépôt contient tous les fichiers d’un projet, ainsi que l’historique de leurs modifications. Vous avez déjà ça ailleurs ? <a href="%s">Migrez-le ici.</a>.
|
||||
|
@ -1228,7 +1254,7 @@ migrate.migrate_items_options=Un jeton d'accès est requis pour migrer des élé
|
|||
migrated_from=Migré de <a href="%[1]s">%[2]s</a>
|
||||
migrated_from_fake=Migré de %[1]s
|
||||
migrate.migrate=Migrer depuis %s
|
||||
migrate.migrating=Migration de <b>%s</b> ...
|
||||
migrate.migrating=Migration de <b>%s</b> …
|
||||
migrate.migrating_failed=La migration de <b>%s</b> a échoué.
|
||||
migrate.migrating_failed.error=Échec de la migration : %s
|
||||
migrate.migrating_failed_no_addr=Échec de la migration.
|
||||
|
@ -1388,7 +1414,7 @@ editor.file_is_a_symlink=`« %s » est un lien symbolique. Ce type de fichiers
|
|||
editor.filename_is_a_directory=« %s » est déjà utilisé comme nom de dossier dans ce dépôt.
|
||||
editor.file_editing_no_longer_exists=Impossible de modifier le fichier « %s » car il n’existe plus dans ce dépôt.
|
||||
editor.file_deleting_no_longer_exists=Impossible de supprimer le fichier « %s » car il n’existe plus dans ce dépôt.
|
||||
editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez commencé à éditer. <a target="_blank" rel="noopener noreferrer" href="%s">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.
|
||||
editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez ouvert le fichier. <a target="_blank" rel="noopener noreferrer" href="%s">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.
|
||||
editor.file_already_exists=Un fichier nommé "%s" existe déjà dans ce dépôt.
|
||||
editor.commit_empty_file_header=Réviser un fichier vide
|
||||
editor.commit_empty_file_text=Le fichier que vous allez réviser est vide. Continuer ?
|
||||
|
@ -2540,7 +2566,7 @@ settings.lfs_invalid_locking_path=Chemin invalide : %s
|
|||
settings.lfs_invalid_lock_directory=Impossible de verrouiller le répertoire : %s
|
||||
settings.lfs_lock_already_exists=Verrou déjà existant : %s
|
||||
settings.lfs_lock=Verrou
|
||||
settings.lfs_lock_path=Chemin de fichier à verrouiller...
|
||||
settings.lfs_lock_path=Chemin de fichier à verrouiller…
|
||||
settings.lfs_locks_no_locks=Pas de verrous
|
||||
settings.lfs_lock_file_no_exist=Le fichier verrouillé n'existe pas dans la branche par défaut
|
||||
settings.lfs_force_unlock=Forcer le déverrouillage
|
||||
|
@ -2732,7 +2758,7 @@ editor.invalid_commit_mail = Courriel invalide pour la création d'un commit.
|
|||
commits.browse_further = Continuer la navigation
|
||||
commits.renamed_from = Renommé depuis %s
|
||||
pulls.nothing_to_compare_have_tag = La branche ou le tag sélectionné sont identiques.
|
||||
issues.blocked_by_user = Vous ne pouvez pas créer un ticket sur ce dépôt car vous avez été bloqué par son propriétaire.
|
||||
issues.blocked_by_user = Vous ne pouvez pas créer de tickets sur ce dépôt car vous avez été bloqué par son propriétaire.
|
||||
pulls.blocked_by_user = Vous ne pouvez pas créer une pull request sur ce dépôt car vous êtes bloqué par son propriétaire.
|
||||
wiki.cancel = Annuler
|
||||
settings.wiki_globally_editable = Permettre l'édition du wiki a tout le monde
|
||||
|
@ -2876,9 +2902,20 @@ summary_card_alt = Carte résumé du dépôt %s
|
|||
archive.pull.noreview = Ce dépôt est archivé. Vous ne pouvez pas faire de revue de demandes d'ajout.
|
||||
editor.commit_email = Courriel de commit
|
||||
commits.view_single_diff = Voir les changements dans ce fichier introduit par ce commit
|
||||
issues.reopen.blocked_by_user = Vous ne pouvez pas ré-ouvrir ce ticket care vous êtes bloqués par le propriétaire du dépôt ou le créateur de ce ticket.
|
||||
migrate.repo_desc_helper = Laisser vide afin d'importer une description existante
|
||||
issues.filter_no_results = Pas de résultats
|
||||
issues.filter_no_results_placeholder = Essayez d'ajuster vos critères de recherche.
|
||||
archive.nocomment = Il n'est pas possible de commenter car le dépôt est archivé.
|
||||
comment.blocked_by_user = Il n'est pas possible de commenter car vous avez été bloqué par le propriétaire du dépôt ou l'auteur.
|
||||
pulls.editable = Editable
|
||||
pulls.editable_explanation = Cette pull request peut être éditée par les mainteneurs. Vous pouvez y contribuer directement.
|
||||
sync_fork.branch_behind_one = Cette branche a %d commits de retard sur %s
|
||||
sync_fork.branch_behind_few = Cettte branche a %d commits de retard sur %s
|
||||
sync_fork.button = Sync
|
||||
|
||||
[graphs]
|
||||
component_loading = Chargement %s...
|
||||
component_loading = Chargement %s…
|
||||
component_loading_failed = Échec de chargement de %s
|
||||
|
||||
component_loading_info = Cela peut prendre du temps…
|
||||
|
@ -3013,8 +3050,8 @@ teams.invite.by=Invité par %s
|
|||
teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindre l’équipe.
|
||||
follow_blocked_user = Vous ne pouvez pas suivre cette organisation car elle vous a bloqué.
|
||||
open_dashboard = Ouvrir le tableau de bord
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.few = L'ancien pseudonyme sera disponible pour n'importe qui après une période de %[1]d jours, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.one = L'ancien pseudonyme sera disponible pour n'importe qui après une période d'%[1]d jour, vous pouvez toujours réclamer votre ancien pseudonyme pendant cette période.
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.few = L'ancien nom d'organisation sera disponible pour n'importe qui après une période de %[1]d jours, vous pouvez toujours réclamer votre ancien nom d'organisation pendant cette période.
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.one = L'ancien nom d'organisation sera disponible pour n'importe qui après une période d'%[1]d jour, vous pouvez toujours réclamer votre ancien nom d'organisation pendant cette période.
|
||||
|
||||
[admin]
|
||||
dashboard=Tableau de bord
|
||||
|
@ -3840,7 +3877,7 @@ alt.registry = Configurez ce registre à partir d'un terminal :
|
|||
alt.registry.install = Pour installer le paquet, exécutez la commande suivante :
|
||||
alt.install = Installer le paquet
|
||||
alt.repository.multiple_groups = Ce paquet est disponible dans plusieurs groupes.
|
||||
alt.setup = Ajouter un dépôt à la liste des dépôts connecté (choisissez l'architecture nécessaire à la place de '_arch') :
|
||||
alt.setup = Ajouter un dépôt à la liste des dépôts connecté (choisissez l'architecture nécessaire à la place de "_arch") :
|
||||
|
||||
[secrets]
|
||||
secrets=Secrets
|
||||
|
@ -4005,7 +4042,7 @@ exact_tooltip = Inclure uniquement les résultats qui correspondent exactement a
|
|||
issue_kind = Rechercher dans les tickets…
|
||||
union = Union
|
||||
union_tooltip = Inclus les résultats contenant au moins un des mots clé séparés par des espaces
|
||||
pull_kind = Rechercher dans les demande d'ajout
|
||||
pull_kind = Rechercher dans les demande d'ajout…
|
||||
milestone_kind = Recherche dans les jalons...
|
||||
regexp_tooltip = Interpréter le terme de recherche comme une expression régulière
|
||||
regexp = RegExp
|
||||
|
@ -4046,4 +4083,4 @@ issues.write = <b>Écrire :</b> Fermer des tickets et gérer les métadonnées t
|
|||
pulls.read = <b>Lire :</b> Lire et créer des demandes de tirage.
|
||||
|
||||
[translation_meta]
|
||||
test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce doux 100 % de complétion :)
|
||||
test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce doux 100 % de complétion. :-)
|
||||
|
|
|
@ -291,3 +291,8 @@ app_slogan = Slogan da instancia
|
|||
app_slogan_helper = Escribe o slogan da túa instancia aqui. Ou deixao baleiro para desabilitala.
|
||||
domain = Dominio do servidor
|
||||
ssh_port = Porto do servidor SSH
|
||||
|
||||
[repo]
|
||||
sync_fork.branch_behind_few = Esta rama ten %d achegas por detrás de %s
|
||||
sync_fork.button = Sincronizar
|
||||
sync_fork.branch_behind_one = Esta rama ten %d achega por detrás de %s
|
|
@ -228,7 +228,7 @@ server_internal = Iekšēja servera kļūda
|
|||
app_desc=Pašmitināms Git pakalpojums bez galvassāpēm
|
||||
install=Viegli uzstādīt
|
||||
install_desc=Vienkārši <a target="_blank" rel="noopener noreferrer" href="%[1]s">jāpalaiž izpildāmā datne</a> vajadzīgajai sistēmai, jāizmanto <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a> vai jāiegūst <a target="_blank" rel="noopener noreferrer" href="%[3]s">pakotne</a>.
|
||||
platform=Pieejama dažādām platformām
|
||||
platform=Dažādas platformas
|
||||
lightweight=Viegla
|
||||
lightweight_desc=Forgejo ir zemas tehniskās prasības, un to var darbināt nedārgā Raspberry Pi datorā. Taupām savas ierīces patērēto enerģiju!
|
||||
license=Atvērtā pirmkoda
|
||||
|
@ -1412,7 +1412,7 @@ editor.file_is_a_symlink=`"%s" ir simboliska saite. Simboliskās saites tīmekļ
|
|||
editor.filename_is_a_directory=Datnes nosaukums "%s" šajā glabātavā jau tiek izmantos kā mapes nosaukums.
|
||||
editor.file_editing_no_longer_exists=Datne, kas tiek labota ("%s"), šajā glabātavā vairs nepastāv.
|
||||
editor.file_deleting_no_longer_exists=Datne, kas tiek izdzēsta ("%s"), šajā glabātavā vairs nepastāv.
|
||||
editor.file_changed_while_editing=Datnes saturs ir mainījies kopš labošanas uzsākšanas. <a target="_blank" rel="noopener noreferrer" href="%s">Klikšķināt šeit</a>, lai apskatītu vai <strong>atkārtoti iesūtītu izmaiņas</strong>, lai tās pārrakstītu.
|
||||
editor.file_changed_while_editing=Datnes saturs ir mainījies kopš tās atvēršanas. <a target="_blank" rel="noopener noreferrer" href="%s">Klikšķināt šeit</a>, lai apskatītu vai <strong>atkārtoti iesūtītu izmaiņas</strong>, lai tās pārrakstītu.
|
||||
editor.file_already_exists=Datne ar nosaukumu "%s" jau pastāv šajā glabātavā.
|
||||
editor.commit_empty_file_header=Iesūtīt tukšu datni
|
||||
editor.commit_empty_file_text=Iesūtāmā datne ir tukša. Turpināt?
|
||||
|
@ -2910,6 +2910,9 @@ issues.filter_no_results_placeholder = Jāmēģina pielāgot meklēšanas atlas
|
|||
migrate.repo_desc_helper = Atstāt tukšu, lai ievietotu esošo aprakstu
|
||||
archive.nocomment = Piebilžu pievienošana nav iespējama, jo glabātava ir arhivēta.
|
||||
comment.blocked_by_user = Piebilžu pievienošana nav iespējama, jo glabātavas īpašnieks vai autors ir nolieguši Tevi.
|
||||
sync_fork.branch_behind_one = Šis zars ir %d iesūtījumu aiz %s
|
||||
sync_fork.button = Sinhronizēt
|
||||
sync_fork.branch_behind_few = Šis zars ir %d iesūtījumus aiz %s
|
||||
|
||||
[graphs]
|
||||
component_loading=Ielādē %s…
|
||||
|
|
|
@ -2611,6 +2611,9 @@ issues.filter_no_results_placeholder = Versöök, diene Söök-Filters antopasse
|
|||
migrate.repo_desc_helper = Leeg laten, um de bestahn Beschrieven to importeren
|
||||
comment.blocked_by_user = Kommenteren gaht hier nich, denn du büst vun de Repositoriums-Eegner of de Autor blockeert worden.
|
||||
archive.nocomment = Kommenteren gaht hier nich, denn dat Repositorium is archiveert.
|
||||
sync_fork.button = Vernejen
|
||||
sync_fork.branch_behind_one = Deeser Twieg is %d Kommitteren achter %s
|
||||
sync_fork.branch_behind_few = Deeser Twieg is %d Kommitterens achter %s
|
||||
|
||||
[repo.permissions]
|
||||
code.read = <b>Lesen:</b> De Quelltext vun deesem Repositorium ankieken un klonen.
|
||||
|
|
|
@ -2909,6 +2909,9 @@ issues.filter_no_results = Geen resultaten
|
|||
migrate.repo_desc_helper = Leeg laten om bestaande beschrijving te importeren
|
||||
archive.nocomment = Commentaar geven is niet mogelijk omdat de repository gearchiveerd is.
|
||||
comment.blocked_by_user = Commentaar geven is niet mogelijk omdat u geblokkeerd bent door de eigenaar van de repository of door de auteur.
|
||||
sync_fork.button = Synchroniseer
|
||||
sync_fork.branch_behind_one = Deze branch is %d commit achter %s
|
||||
sync_fork.branch_behind_few = Deze branch is %d commits achter %s
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1405,7 +1405,7 @@ editor.file_is_a_symlink=`"%s" é um link simbólico. Links simbólicos não pod
|
|||
editor.filename_is_a_directory=O nome do arquivo "%s" já é usado como um nome de diretório neste repositório.
|
||||
editor.file_editing_no_longer_exists=O arquivo que está sendo editado, "%s", não existe mais neste repositório.
|
||||
editor.file_deleting_no_longer_exists=O arquivo a ser excluído, "%s", não existe mais neste repositório.
|
||||
editor.file_changed_while_editing=O conteúdo do arquivo mudou desde que você começou a editar. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as diferenças ou <strong>clique em Aplicar commit das alterações novamente</strong> para sobrescrever as alterações com sua versão atual.
|
||||
editor.file_changed_while_editing=O conteúdo do arquivo mudou desde que você abriu o arquivo. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as diferenças ou <strong>clique em Aplicar commit das alterações novamente</strong> para sobrescrever as alterações com sua versão atual.
|
||||
editor.file_already_exists=Um arquivo com nome "%s" já existe neste repositório.
|
||||
editor.commit_empty_file_header=Fazer commit de um arquivo vazio
|
||||
editor.commit_empty_file_text=O arquivo que você está prestes fazer commit está vazio. Continuar?
|
||||
|
@ -2910,6 +2910,9 @@ issues.filter_no_results_placeholder = Tente ajustar seus filtros de pesquisa.
|
|||
archive.nocomment = Não é possível comentar pois o repositório foi arquivado.
|
||||
migrate.repo_desc_helper = Deixe em branco para importar a descrição existente
|
||||
comment.blocked_by_user = Não é possível comentar pois você foi bloqueado pelo dono ou autor do repositório.
|
||||
sync_fork.branch_behind_few = Esta branch está %d commit(s) atrás de %s
|
||||
sync_fork.branch_behind_one = Esta branch está %d commit(s) atrás de %s
|
||||
sync_fork.button = Sincronizar
|
||||
|
||||
[graphs]
|
||||
component_loading = Carregando %s…
|
||||
|
|
|
@ -1418,7 +1418,7 @@ editor.file_is_a_symlink=`"%s" é uma ligação simbólica. Ligações simbólic
|
|||
editor.filename_is_a_directory=O nome de ficheiro "%s" já está a ser usado como um nome de pasta neste repositório.
|
||||
editor.file_editing_no_longer_exists=O ficheiro que está a ser editado, "%s", já não existe neste repositório.
|
||||
editor.file_deleting_no_longer_exists=O ficheiro que está a ser eliminado, "%s", já não existe neste repositório.
|
||||
editor.file_changed_while_editing=O conteúdo do ficheiro mudou desde que começou a editar. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as modificações ou clique em <strong>Cometer modificações novamente</strong> para escrever por cima.
|
||||
editor.file_changed_while_editing=O conteúdo do ficheiro mudou desde que abriu o ficheiro. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as modificações ou <strong>Cometer modificações novamente</strong> para escrever por cima.
|
||||
editor.file_already_exists=Já existe um ficheiro com o nome "%s" neste repositório.
|
||||
editor.commit_empty_file_header=Cometer um ficheiro vazio
|
||||
editor.commit_empty_file_text=O ficheiro que está prestes a cometer está vazio. Quer continuar?
|
||||
|
@ -2912,6 +2912,9 @@ issues.filter_no_results_placeholder = Tente ajustar os seus filtros de pesquisa
|
|||
migrate.repo_desc_helper = Deixe em branco para importar a descrição existente
|
||||
archive.nocomment = Não é possível fazer comentários porque o repositório está arquivado.
|
||||
comment.blocked_by_user = Não é possível comentar porque está bloqueado pelo proprietário do repositório ou pelo autor.
|
||||
sync_fork.branch_behind_few = Este ramo está %d cometimentos atrás de %s
|
||||
sync_fork.button = Sincronizar
|
||||
sync_fork.branch_behind_one = Este ramo está %d cometimento atrás de %s
|
||||
|
||||
[graphs]
|
||||
component_loading=A carregar %s…
|
||||
|
|
|
@ -1399,7 +1399,7 @@ editor.file_is_a_symlink=`«%s» является символической с
|
|||
editor.filename_is_a_directory=Имя файла «%s» уже используется в качестве каталога в этом репозитории.
|
||||
editor.file_editing_no_longer_exists=Редактируемый файл «%s» больше не существует в этом репозитории.
|
||||
editor.file_deleting_no_longer_exists=Удаляемый файл «%s» больше не существует в этом репозитории.
|
||||
editor.file_changed_while_editing=Содержимое файла изменилось с момента начала редактирования. <a target="_blank" rel="noopener noreferrer" href="%s">Нажмите здесь</a>, чтобы увидеть, что было изменено, или <strong>Зафиксировать изменения снова</strong>, чтобы заменить их.
|
||||
editor.file_changed_while_editing=Содержимое файла изменилось после того, как он был открыт. <a target="_blank" rel="noopener noreferrer" href="%s">Ознакомьтесь</a> с произошедшими изменениями или <strong>сохраните ещё раз</strong>, чтобы перезаписать их.
|
||||
editor.file_already_exists=Файл с названием «%s» уже существует в этом репозитории.
|
||||
editor.commit_empty_file_header=Закоммитить пустой файл
|
||||
editor.commit_empty_file_text=Файл, который вы собираетесь зафиксировать, пуст. Продолжить?
|
||||
|
@ -2237,11 +2237,11 @@ settings.trust_model.default.desc=Использовать фактор дове
|
|||
settings.trust_model.collaborator=Соучастник
|
||||
settings.trust_model.collaborator.long=Соучастник: доверять подписям соучастников
|
||||
settings.trust_model.collaborator.desc=Действительные подписи соучастников этого репозитория будут помечены как «доверенные» (независимо от того, соответствуют ли они автору коммита). В остальных случаях действительные подписи будут помечены как «недоверенные», если подпись соответствует автору коммита, и «не совпадающие», если нет.
|
||||
settings.trust_model.committer=Коммитер
|
||||
settings.trust_model.committer.long=Коммитер: доверять подписям, соответствующим коммитерам (соответствует GitHub и требует коммиты, подписанные Forgejo, иметь Forgejo в качестве коммитера)
|
||||
settings.trust_model.committer=Автор коммита
|
||||
settings.trust_model.committer.long=Автор коммита: доверять подписям, соответствующим авторам коммитов. Это поведение соответствует GitHub и требует, чтобы коммиты, подписанные Forgejo, имели Forgejo в качестве автора
|
||||
settings.trust_model.committer.desc=Действительные подписи будут помечены «доверенными», только если они соответствуют автору коммита, в противном случае они будут помечены «не совпадающими». Это заставит Forgejo быть автором подписанных коммитов, а фактический автор будет обозначен в трейлерах Co-Authored-By: и Co-Committed-By: коммита. Ключ Forgejo по умолчанию должен соответствовать пользователю в базе данных.
|
||||
settings.trust_model.collaboratorcommitter=Соучастник+Коммитер
|
||||
settings.trust_model.collaboratorcommitter.long=Соучастник+Коммитер: доверять подписям соучастников, которые соответствуют автору коммита
|
||||
settings.trust_model.collaboratorcommitter=Соучастник и автор коммита
|
||||
settings.trust_model.collaboratorcommitter.long=Соучастник и автор коммита: доверять подписям соучастников, которые соответствуют автору коммита
|
||||
settings.trust_model.collaboratorcommitter.desc=Действительные подписи соучастников этого репозитория будут помечены «доверенными», если они соответствуют автору коммита. Действительные подписи будут помечены как «недоверенные», если подпись соответствует автору коммита, и «не совпадающие» впротивном случае. Это заставит Forgejo быть отмеченным в качестве автора подписанного коммита, а фактический автор будет указан в трейлерах Co-Authored-By: и Co-Committed-By: коммита. Ключ Forgejo по умолчанию должен соответствовать пользователю в базе данных.
|
||||
settings.wiki_delete=Стереть данные вики
|
||||
settings.wiki_delete_desc=Будьте внимательны! Как только вы удалите вики — пути назад не будет.
|
||||
|
@ -2913,6 +2913,9 @@ issues.filter_no_results_placeholder = Попробуйте поискать п
|
|||
migrate.repo_desc_helper = Оставьте пустым, чтобы скопировать описание из источника
|
||||
archive.nocomment = Комментирование невозможно, потому что этот репозиторий архивирован.
|
||||
comment.blocked_by_user = Комментирование невозможно, потому что вы заблокированы его владельцем или автором обсуждения.
|
||||
sync_fork.branch_behind_few = Эта ветвь отстаёт от %s на %d коммитов
|
||||
sync_fork.button = Синхронизировать
|
||||
sync_fork.branch_behind_one = Эта ветвь отстаёт от %s на %d коммит
|
||||
|
||||
[graphs]
|
||||
component_loading_failed = Не удалось загрузить %s
|
||||
|
@ -3697,7 +3700,7 @@ no_subscriptions=Нет подписок
|
|||
default_key=Подписано ключом по умолчанию
|
||||
error.extract_sign=Не удалось извлечь подпись
|
||||
error.generate_hash=Не удалось создать хеш коммита
|
||||
error.no_committer_account=Учётная запись с эл. почтой этого коммитера не найдена
|
||||
error.no_committer_account=Учётная запись с эл. почтой автора этого коммита не найдена
|
||||
error.no_gpg_keys_found=Не найден ключ, соответствующий данной подписи
|
||||
error.not_signed_commit=Неподписанный коммит
|
||||
error.failed_retrieval_gpg_keys=Не удалось получить ни одного ключа GPG автора коммита
|
||||
|
@ -4054,7 +4057,7 @@ union_tooltip = Включает результаты с совпавшими к
|
|||
union = Обычный
|
||||
milestone_kind = Найти этапы...
|
||||
regexp = Регулярное выражение
|
||||
regexp_tooltip = Интерпретировать поисковый запрос как регулярное выражение
|
||||
regexp_tooltip = Поисковый запрос будет воспринят как регулярное выражение
|
||||
|
||||
|
||||
[markup]
|
||||
|
|
|
@ -231,7 +231,7 @@ platform_desc=Forgejo підтверджено працює на вільних
|
|||
lightweight=Невибагливість
|
||||
lightweight_desc=Forgejo має низькі вимоги до ресурсів та може працювати на недорогому Raspberry Pi. Заощадьте енергію свого комп'ютера!
|
||||
license=Відкритий вихідний код
|
||||
license_desc=Відвідайте <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Приєднайтесь до нас та <a target="_blank" rel="noopener noreferrer" href="%[2]s">зробіть свій внесок</a> до проєкту, щоб зробити його ще краще. Не бійтеся долучитися!
|
||||
license_desc=Відвідайте <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Приєднуйтесь до нас та <a target="_blank" rel="noopener noreferrer" href="%[2]s">зробіть свій внесок</a>, щоб покращити проєкт ще більше. Не бійтеся долучитися!
|
||||
install_desc = Просто <a target="_blank" rel="noopener noreferrer" href="%[1]s">запустіть уже зібрану програму</a> для своєї платформи, розгорніть її за допомогою <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a> або встановіть <a target="_blank" rel="noopener noreferrer" href="%[3]s">пакунок</a>.
|
||||
|
||||
[install]
|
||||
|
@ -299,7 +299,7 @@ disable_gravatar.description=Вимкнути Gravatar або інші стор
|
|||
federated_avatar_lookup=Увімкнути федеровані аватари
|
||||
federated_avatar_lookup.description=Увімкнути зовнішні аватари за допомогою Libravatar.
|
||||
disable_registration=Вимкнути самостійну реєстрацію
|
||||
disable_registration.description=Тільки адміністратор може створювати нові облікові записи. Наполегливо рекомендуємо залишити реєстрацію вимкненою, якщо ви не збираєтеся розміщувати загальнодоступний екземпляр та сприяти появі величезної кількості спам-акаунтів.
|
||||
disable_registration.description=Тільки адміністратор може створювати нові облікові записи. Настійно рекомендуємо залишити реєстрацію вимкненою, якщо ви не збираєтеся розміщувати загальнодоступний екземпляр та сприяти появі величезної кількості спам-акаунтів.
|
||||
allow_only_external_registration.description=Користувачам буде дозволено реєструватись лише через налаштовані сторонні сервіси.
|
||||
openid_signin=Увімкнути реєстрацію за допомогою OpenID
|
||||
openid_signin.description=Увімкнути вхід за допомогою OpenID.
|
||||
|
@ -2659,6 +2659,9 @@ release.asset_name = Назва ресурсу
|
|||
release.add_external_asset = Додати зовнішній ресурс
|
||||
find_file.no_matching = Не знайдено відповідного файлу
|
||||
commits.search.tooltip = До ключових слів можна додавати префікси «author:», «committer:», «after:» або «before:», наприклад, «revert author:Alice before:2019-01-13».
|
||||
sync_fork.button = Синхронізувати
|
||||
sync_fork.branch_behind_one = Ця гілка на %d коміт позаду %s
|
||||
sync_fork.branch_behind_few = Ця гілка на %d комітів позаду %s
|
||||
|
||||
[graphs]
|
||||
contributors.what = внески
|
||||
|
|
|
@ -285,7 +285,7 @@ log_root_path=日志路径
|
|||
log_root_path_helper=日志文件将写入此目录。
|
||||
|
||||
optional_title=可选设置
|
||||
email_title=电子邮箱设置
|
||||
email_title=电子邮件设置
|
||||
smtp_addr=SMTP 主机地址
|
||||
smtp_port=SMTP 端口
|
||||
smtp_from=电子邮件发件人
|
||||
|
@ -322,12 +322,12 @@ install_btn_confirm=立即安装
|
|||
test_git_failed=无法识别 “git” 命令:%v
|
||||
sqlite3_not_available=当前 Forgejo 版本不支持 SQLite3。请从 %s 下载官方构建版(注:请勿下载标有 “gobuild” 的版本)。
|
||||
invalid_db_setting=数据库设置无效:%v
|
||||
invalid_db_table=数据库表 '%s' 无效: %v
|
||||
invalid_db_table=数据库表 '%s' 无效:%v
|
||||
invalid_repo_path=仓库根目录设置无效:%v
|
||||
invalid_app_data_path=应用数据路径无效: %v
|
||||
invalid_app_data_path=应用数据路径无效:%v
|
||||
run_user_not_match=运行用户名不是当前的用户名:%s -> %s
|
||||
internal_token_failed=生成内部令牌失败: %v
|
||||
secret_key_failed=生成密钥失败: %v
|
||||
internal_token_failed=生成内部令牌失败:%v
|
||||
secret_key_failed=生成密钥失败:%v
|
||||
save_config_failed=应用配置保存失败:%v
|
||||
invalid_admin_setting=管理员帐户设置无效:%v
|
||||
invalid_log_root_path=日志路径无效:%v
|
||||
|
@ -354,7 +354,7 @@ app_slogan = 实例标语
|
|||
app_slogan_helper = 在此处输入您的实例标语。留空则禁用。
|
||||
|
||||
[home]
|
||||
uname_holder=用户名或电子邮箱
|
||||
uname_holder=用户名或电子邮件地址
|
||||
password_holder=密码
|
||||
switch_dashboard_context=切换控制面板用户
|
||||
my_repos=仓库列表
|
||||
|
@ -479,7 +479,7 @@ password_pwned_err=无法完成对 HaveIBeenPwned 的请求
|
|||
last_admin=您不能删除最后一个管理员。必须至少保留一个管理员。
|
||||
change_unconfirmed_email = 如果您在注册时提供了错误的邮箱地址,您可以在下方修改,激活邮件会发送到修改后的邮箱地址。
|
||||
change_unconfirmed_email_summary = 修改用来接收激活邮件的邮箱地址。
|
||||
change_unconfirmed_email_error = 无法修改邮箱地址: %v
|
||||
change_unconfirmed_email_error = 无法修改邮箱地址:%v
|
||||
tab_signin = 登录
|
||||
tab_signup = 注册
|
||||
hint_login = 已经有账户了吗?<a href="%s">立即登录!</a>
|
||||
|
@ -537,7 +537,7 @@ issue.in_tree_path=在 %s 中:
|
|||
|
||||
release.new.subject=%[2]s 中的 %[1]s 发布了
|
||||
release.new.text=<b>@%[1]s</b> 于 %[3]s 发布了 %[2]s
|
||||
release.title=标题: %s
|
||||
release.title=标题:%s
|
||||
release.note=注释:
|
||||
release.downloads=下载:
|
||||
release.download.zip=源代码(ZIP)
|
||||
|
@ -617,7 +617,7 @@ include_error=`必须包含子字符串 "%s"。`
|
|||
glob_pattern_error=`匹配模式无效:%s.`
|
||||
regex_pattern_error=`正则表达式无效:%s.`
|
||||
username_error=` 只允许包含字母数字字符(“0-9”、“a-z”、“A-Z”)、破折号(“-”)、下划线(“_”)和点(“.”)。不能以非字母数字字符开头或结尾,并且不允许连续的非字母数字字符。`
|
||||
invalid_group_team_map_error=`映射无效: %s`
|
||||
invalid_group_team_map_error=`映射无效:%s`
|
||||
unknown_error=未知错误:
|
||||
captcha_incorrect=验证码不正确。
|
||||
password_not_match=密码不匹配。
|
||||
|
@ -659,7 +659,7 @@ organization_leave_success=您已成功离开组织 %s。
|
|||
|
||||
invalid_ssh_key=无法验证您的 SSH 密钥:%s
|
||||
invalid_gpg_key=无法验证您的 GPG 密钥:%s
|
||||
invalid_ssh_principal=无效的规则: %s
|
||||
invalid_ssh_principal=无效的规则:%s
|
||||
must_use_public_key=您提供的密钥是私钥。不要在任何地方上传您的私钥,请改用您的公钥。
|
||||
unable_verify_ssh_key=无法验证 SSH 密钥,请仔细检查是否有错误。
|
||||
auth_failed=授权验证失败:%v
|
||||
|
@ -685,7 +685,7 @@ Description = 描述
|
|||
Pronouns = 代称
|
||||
Biography = 简历
|
||||
username_claiming_cooldown = 用户名不能被认领,因为其仍处于保护期。其可以在%[1]s后被认领。
|
||||
email_domain_is_not_allowed = 用户电子邮箱的域名<b>%s</b>与EMAIL_DOMAIN_ALLOWLIST或EMAIL_DOMAIN_BLOCKLIST冲突。请确保您正确设置了电子邮件地址。
|
||||
email_domain_is_not_allowed = 用户电子邮件地址的域名<b>%s</b>与EMAIL_DOMAIN_ALLOWLIST或EMAIL_DOMAIN_BLOCKLIST冲突。请确保您正确设置了电子邮件地址。
|
||||
|
||||
[user]
|
||||
change_avatar=修改头像…
|
||||
|
@ -794,7 +794,7 @@ privacy=隐私设置
|
|||
keep_activity_private=隐藏个人资料页面中的活动
|
||||
keep_activity_private_popup=您的活动将只对您自己和本实例的管理员可见
|
||||
|
||||
lookup_avatar_by_mail=使用电子邮箱地址查找头像
|
||||
lookup_avatar_by_mail=使用电子邮件地址查找头像
|
||||
federated_avatar_lookup=查找联合头像
|
||||
enable_custom_avatar=使用自定义头像
|
||||
choose_new_avatar=选择新的头像
|
||||
|
@ -828,8 +828,8 @@ activations_pending=等待激活
|
|||
can_not_add_email_activations_pending=有一个待处理的激活请求,请稍等几分钟后再尝试添加新的电子邮件地址。
|
||||
delete_email=移除
|
||||
email_deletion=移除电子邮件地址
|
||||
email_deletion_desc=电子邮箱地址和相关信息将会被删除。使用此电子邮箱地址发送的Git提交将会保留,继续?
|
||||
email_deletion_success=您的电子邮箱地址已被移除。
|
||||
email_deletion_desc=电子邮件地址和相关信息将会被删除。使用此电子邮件地址发送的Git提交将会保留,继续?
|
||||
email_deletion_success=您的电子邮件地址已被移除。
|
||||
theme_update_success=您的主题已更新。
|
||||
theme_update_error=所选主题不存在。
|
||||
openid_deletion=移除 OpenID 地址
|
||||
|
@ -1459,7 +1459,7 @@ commits.view_path=在历史记录中的此处查看
|
|||
|
||||
commit.operations=操作
|
||||
commit.revert=还原
|
||||
commit.revert-header=还原: %s
|
||||
commit.revert-header=还原:%s
|
||||
commit.revert-content=选择要还原的分支:
|
||||
commit.cherry-pick=拣选
|
||||
commit.cherry-pick-header=Cherry-pick:%s
|
||||
|
@ -2279,7 +2279,7 @@ settings.trust_model.collaborator=协作者
|
|||
settings.trust_model.collaborator.long=协作者:信任协作者的签名
|
||||
settings.trust_model.collaborator.desc=此仓库中协作者的有效签名将被标记为「可信」(无论它们是否是提交者),签名只符合提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。
|
||||
settings.trust_model.committer=提交者
|
||||
settings.trust_model.committer.long=提交者: 信任与提交者相符的签名(此特性类似 GitHub,这会强制采用 Forgejo 作为提交者和签名者)
|
||||
settings.trust_model.committer.long=提交者:信任与提交者相符的签名(此特性类似 GitHub,这会强制采用 Forgejo 作为提交者和签名者)
|
||||
settings.trust_model.committer.desc=有效签名只有和提交者相匹配才会被标记为“受信任”,否则它们将被标记为“不匹配”。这强制 Forgejo 成为签名提交的提交者,而实际提交者被加上 Co-authored-by:和 Co-committed-by:的标记。 默认的 Forgejo 密钥必须匹配数据库中的一名用户。
|
||||
settings.trust_model.collaboratorcommitter=协作者+提交者
|
||||
settings.trust_model.collaboratorcommitter.long=协作者+提交者:信任协作者同时是提交者的签名
|
||||
|
@ -2500,9 +2500,9 @@ settings.protect_branch_name_pattern=受保护的分支名称正则
|
|||
settings.protect_branch_name_pattern_desc=受保护的分支名称正则。语法请参阅<a href="%s">文档</a> 。如:main, release/**
|
||||
settings.protect_patterns=规则
|
||||
settings.protect_protected_file_patterns=受保护的文件模式(使用半角分号“;”分隔)
|
||||
settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用半角分号(“;”)分隔多个模式。 见<a href="%[1]s">%[2]s</a>文档了解模式语法。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
|
||||
settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用半角分号(“;”)分隔多个模式。 见<a href="%[1]s">%[2]s</a>文档了解模式语法。例如:<code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
|
||||
settings.protect_unprotected_file_patterns=不受保护的文件模式(使用半角分号“;”分隔)
|
||||
settings.protect_unprotected_file_patterns_desc=在用户有写权限的情况下允许绕过限制,直接修改设为不保护的文件。如有多个匹配模式,则可用半角分号(“;”)分隔开。见 <a href="%[1]s">%[2]s</a> 的文档以了解匹配模式的格式。例子: <code>.drone.yml</code>、<code>/docs/**/*.txt</code>。
|
||||
settings.protect_unprotected_file_patterns_desc=在用户有写权限的情况下允许绕过限制,直接修改设为不保护的文件。如有多个匹配模式,则可用半角分号(“;”)分隔开。见 <a href="%[1]s">%[2]s</a> 的文档以了解匹配模式的格式。例子:<code>.drone.yml</code>、<code>/docs/**/*.txt</code>。
|
||||
settings.add_protected_branch=启用保护
|
||||
settings.delete_protected_branch=禁用保护
|
||||
settings.update_protect_branch_success=分支保护规则 %s 更新成功。
|
||||
|
@ -2912,6 +2912,9 @@ issues.filter_no_results_placeholder = 尝试调整搜索筛选条件。
|
|||
migrate.repo_desc_helper = 留空以导入现有描述
|
||||
archive.nocomment = 您无法评论,因为此仓库已存档。
|
||||
comment.blocked_by_user = 您无法评论,因为您已被仓库所有者或作者屏蔽。
|
||||
sync_fork.button = 同步
|
||||
sync_fork.branch_behind_one = 此分支落后于 %s %d 个提交
|
||||
sync_fork.branch_behind_few = 此分支落后于 %s %d 个提交
|
||||
|
||||
[graphs]
|
||||
component_loading=正在加载 %s…
|
||||
|
@ -3084,11 +3087,11 @@ dashboard.task.process=任务:%[1]s
|
|||
dashboard.task.cancelled=任务:%[1]s 已取消:%[3]s
|
||||
dashboard.task.error=任务中的错误:%[1]s:%[3]s
|
||||
dashboard.task.finished=任务:%[2]s 启动的 %[1]s 已完成
|
||||
dashboard.task.unknown=未知任务: %[1]s
|
||||
dashboard.task.unknown=未知任务:%[1]s
|
||||
dashboard.cron.started=已开始计划任务:%[1]s
|
||||
dashboard.cron.process=计划任务:%[1]s
|
||||
dashboard.cron.cancelled=定时任务:%[1]s 已取消:%[3]s
|
||||
dashboard.cron.error=任务中的错误: %s:%[3]s
|
||||
dashboard.cron.error=任务中的错误:%s:%[3]s
|
||||
dashboard.cron.finished=任务:%[1]s 已经完成
|
||||
dashboard.delete_inactive_accounts=删除所有未激活的帐户
|
||||
dashboard.delete_inactive_accounts.started=删除所有未激活的账户任务已启动。
|
||||
|
@ -3222,7 +3225,7 @@ emails.filter_sort.email_reverse=电子邮件(逆序)
|
|||
emails.filter_sort.name=用户名
|
||||
emails.filter_sort.name_reverse=用户名(倒序)
|
||||
emails.updated=电子邮件已更新
|
||||
emails.not_updated=无法更新请求的电子邮件地址: %v
|
||||
emails.not_updated=无法更新请求的电子邮件地址:%v
|
||||
emails.duplicate_active=此电子邮件地址已被另一个用户激活使用。
|
||||
emails.change_email_header=更新电子邮件属性
|
||||
emails.change_email_text=您确定要更新该电子邮件地址吗?
|
||||
|
@ -3248,7 +3251,7 @@ repos.lfs_size=LFS 大小
|
|||
|
||||
packages.package_manage_panel=软件包管理
|
||||
packages.total_size=总大小:%s
|
||||
packages.unreferenced_size=未引用大小: %s
|
||||
packages.unreferenced_size=未引用大小:%s
|
||||
packages.cleanup=清理过期数据
|
||||
packages.cleanup.success=清理过期数据成功
|
||||
packages.owner=所有者
|
||||
|
@ -3291,7 +3294,7 @@ auths.attribute_username=用户名属性
|
|||
auths.attribute_username_placeholder=置空将使用Forgejo的用户名。
|
||||
auths.attribute_name=名字属性
|
||||
auths.attribute_surname=姓氏属性
|
||||
auths.attribute_mail=电子邮箱属性
|
||||
auths.attribute_mail=电子邮件地址属性
|
||||
auths.attribute_ssh_public_key=SSH公钥属性
|
||||
auths.attribute_avatar=头像属性
|
||||
auths.attributes_in_bind=从 bind DN 中拉取属性信息
|
||||
|
@ -3345,7 +3348,7 @@ auths.oauth2_group_claim_name=用于提供用户组名称的 Claim 声明名称
|
|||
auths.oauth2_admin_group=管理员用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
|
||||
auths.oauth2_restricted_group=受限用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
|
||||
auths.oauth2_map_group_to_team=映射声明的组到组织团队。(可选 - 要求在上面填写声明的名字)
|
||||
auths.oauth2_map_group_to_team_removal=如果用户不属于相应的组,从已同步团队中移除用户
|
||||
auths.oauth2_map_group_to_team_removal=如果用户不属于相应的组,则从同步的团队中移除用户。
|
||||
auths.enable_auto_register=允许自动注册
|
||||
auths.sspi_auto_create_users=自动创建用户
|
||||
auths.sspi_auto_create_users_helper=允许 SSPI 认证在用户第一次登录时自动创建新账号
|
||||
|
@ -3362,7 +3365,7 @@ auths.tips.oauth2.general=OAuth2 认证
|
|||
auths.tips.oauth2.general.tip=当注册新的 OAuth2 身份验证时,回调/重定向 URL 应该是:
|
||||
auths.tip.oauth2_provider=OAuth2 提供程序
|
||||
auths.tip.bitbucket=`在 %s
|
||||
auths.tip.nextcloud=使用下面的菜单“设置(Settings) -> 安全(Security) -> OAuth 2.0 client”在您的实例上注册一个新的 OAuth 客户端。
|
||||
auths.tip.nextcloud=使用菜单“设置->安全->OAuth 2.0客户端”在您的实例上注册一个新的 OAuth 客户端。
|
||||
auths.tip.dropbox=在 %s 上创建一个新的应用程序
|
||||
auths.tip.facebook=`在 %s 注册一个新的应用,并添加产品"Facebook 登录"`
|
||||
auths.tip.github=在 %s 注册一个 OAuth 应用程序
|
||||
|
@ -3386,7 +3389,7 @@ auths.still_in_used=认证源仍在使用。请先解除或者删除使用此认
|
|||
auths.deletion_success=认证源已经更新。
|
||||
auths.login_source_exist=认证源 '%s' 已经存在。
|
||||
auths.login_source_of_type_exist=此类型的认证源已存在。
|
||||
auths.unable_to_initialize_openid=无法初始化 OpenID Connect 提供商: %s
|
||||
auths.unable_to_initialize_openid=无法初始化 OpenID Connect 提供商:%s
|
||||
auths.invalid_openIdConnectAutoDiscoveryURL=无效的 Auto Discovery URL(这必须是一个以 http:// 或 https://开头的有效的 URL)
|
||||
|
||||
config.server_config=服务器配置
|
||||
|
@ -3547,7 +3550,7 @@ monitor.process.cancel_notices=中止:<strong>%s</strong> ?
|
|||
monitor.process.children=子进程
|
||||
|
||||
monitor.queues=队列
|
||||
monitor.queue=队列: %s
|
||||
monitor.queue=队列:%s
|
||||
monitor.queue.name=名称
|
||||
monitor.queue.type=类型
|
||||
monitor.queue.exemplar=数据类型
|
||||
|
@ -3664,7 +3667,7 @@ raw_minutes=分钟
|
|||
[dropzone]
|
||||
default_message=拖放文件或点击此处上传。
|
||||
invalid_input_type=您不能上传该类型的文件。
|
||||
file_too_big=文件体积({{filesize}} MB)超过了最大允许体积({{maxFilesize}} MB)
|
||||
file_too_big=文件体积({{filesize}} MB)超过了最大允许体积({{maxFilesize}} MB)。
|
||||
remove_file=移除文件
|
||||
|
||||
[notification]
|
||||
|
@ -3695,7 +3698,7 @@ error.probable_bad_default_signature=警告!虽然默认密钥拥有此ID,
|
|||
[units]
|
||||
unit=单元
|
||||
error.no_unit_allowed_repo=您没有被允许访问此仓库的任何单元。
|
||||
error.unit_not_allowed=您没有权限访问此仓库单元
|
||||
error.unit_not_allowed=您没有权限访问此仓库单元。
|
||||
|
||||
[packages]
|
||||
title=软件包
|
||||
|
@ -3722,7 +3725,7 @@ details.project_site=项目站点
|
|||
details.repository_site=仓库网站
|
||||
details.documentation_site=文档站点
|
||||
details.license=许可协议
|
||||
assets=文件
|
||||
assets=资源
|
||||
versions=版本
|
||||
versions.view_all=查看全部
|
||||
dependency.id=ID
|
||||
|
@ -3819,7 +3822,7 @@ settings.delete.error=删除软件包失败。
|
|||
owner.settings.cargo.title=Cargo 注册中心索引
|
||||
owner.settings.cargo.initialize=初始化索引
|
||||
owner.settings.cargo.initialize.description=使用 Cargo 注册中心时需要一个特殊索引的 Git 仓库。使用此选项将(重新)创建仓库并自动配置它。
|
||||
owner.settings.cargo.initialize.error=初始化Cargo索引失败: %v
|
||||
owner.settings.cargo.initialize.error=初始化Cargo索引失败:%v
|
||||
owner.settings.cargo.initialize.success=Cargo索引已经成功创建。
|
||||
owner.settings.cargo.rebuild=重建索引
|
||||
owner.settings.cargo.rebuild.description=如果索引与存储的 Cargo 包不同步,重建可能会有用。
|
||||
|
@ -3878,11 +3881,11 @@ alt.repository.multiple_groups = 此软件包在多个组中可用。
|
|||
|
||||
[secrets]
|
||||
secrets=密钥
|
||||
description=Secrets 将被传给特定的 Actions,其它情况将不能读取
|
||||
description=机密将被传给特定的 Action,其它情况将不能被读取。
|
||||
none=还没有密钥。
|
||||
creation=添加密钥
|
||||
creation.name_placeholder=不区分大小写,只能包含英文字母、数字或下划线,不能以 GITEA_ 或 GITHUB_ 开头
|
||||
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
|
||||
creation.value_placeholder=输入任何内容。开头和结尾的空格都会被省略。
|
||||
creation.success=您的密钥 '%s' 添加成功。
|
||||
creation.failed=添加密钥失败。
|
||||
deletion=删除密钥
|
||||
|
@ -3917,7 +3920,7 @@ runners.description=组织描述
|
|||
runners.labels=标签
|
||||
runners.last_online=上次在线时间
|
||||
runners.runner_title=运行器
|
||||
runners.task_list=最近在此runner上的任务
|
||||
runners.task_list=最近在此运行器上的任务
|
||||
runners.task_list.no_tasks=还没有任务。
|
||||
runners.task_list.run=执行
|
||||
runners.task_list.status=状态
|
||||
|
@ -3932,8 +3935,8 @@ runners.delete_runner=删除运行器
|
|||
runners.delete_runner_success=运行器删除成功
|
||||
runners.delete_runner_failed=删除运行器失败
|
||||
runners.delete_runner_header=确认要删除此运行器
|
||||
runners.delete_runner_notice=如果一个任务正在运行在此运行器上,它将被终止并标记为失败。它可能会打断正在构建的工作流。
|
||||
runners.none=无可用的 Runner
|
||||
runners.delete_runner_notice=如果有任务正在此运行器上运行,它将被终止并标记为失败。这可能会中断正在构建的工作流。
|
||||
runners.none=无可用的运行器
|
||||
runners.status.unspecified=未知
|
||||
runners.status.idle=空闲
|
||||
runners.status.active=激活
|
||||
|
@ -3946,8 +3949,8 @@ runs.all_workflows=所有工作流
|
|||
runs.commit=提交
|
||||
runs.scheduled=已计划的
|
||||
runs.pushed_by=推送者
|
||||
runs.invalid_workflow_helper=工作流配置文件无效。请检查您的配置文件: %s
|
||||
runs.no_matching_online_runner_helper=没有匹配标签的在线 runner: %s
|
||||
runs.invalid_workflow_helper=工作流配置文件无效。请检查您的配置文件:%s
|
||||
runs.no_matching_online_runner_helper=没有匹配标签的在线运行器:%s
|
||||
runs.actor=操作者
|
||||
runs.status=状态
|
||||
runs.actors_no_select=所有操作者
|
||||
|
@ -3973,7 +3976,7 @@ variables.creation=添加变量
|
|||
variables.none=目前还没有变量。
|
||||
variables.deletion=删除变量
|
||||
variables.deletion.description=删除变量是永久性的,无法撤消。继续吗?
|
||||
variables.description=变量将被传给特定的 Actions,其它情况将不能读取
|
||||
variables.description=变量将被传给特定的 Action,其它情况将不能被读取。
|
||||
variables.id_not_exist=ID为 %d 的变量不存在。
|
||||
variables.edit=编辑变量
|
||||
variables.deletion.failed=删除变量失败。
|
||||
|
|
|
@ -206,6 +206,7 @@ buttons.new_table.tooltip = 新增表格
|
|||
table_modal.header = 新增表格
|
||||
buttons.indent.tooltip = 使項目縮排一層
|
||||
buttons.unindent.tooltip = 使項目取消縮排一層
|
||||
link_modal.header = 新增連結
|
||||
|
||||
[filter]
|
||||
string.asc=A - Z
|
||||
|
@ -2857,6 +2858,9 @@ settings.units.units = 功能
|
|||
diff.git-notes.add = 增加註釋
|
||||
diff.git-notes.remove-header = 移除註釋
|
||||
settings.event_pull_request_enforcement = 執行
|
||||
sync_fork.branch_behind_few = 此分支落後 %s %d 次提交
|
||||
sync_fork.button = 同步
|
||||
sync_fork.branch_behind_one = 此分支落後 %s %d 次提交
|
||||
|
||||
[graphs]
|
||||
component_loading = %s載入中…
|
||||
|
|
|
@ -1,21 +1,4 @@
|
|||
# Forgejo translations
|
||||
|
||||
This directory contains all .INI translations.
|
||||
|
||||
## Working on base language
|
||||
|
||||
When you work on Forgejo features, you should only modify `locale_en-US.ini`.
|
||||
|
||||
* consult https://forgejo.org/docs/next/contributor/localization-english/
|
||||
* add strings when your change requires doing so
|
||||
* remove strings when your change renders them unused
|
||||
|
||||
## Working on other languages
|
||||
|
||||
Translations are done on Codeberg Translate and not via individual pull requests.
|
||||
|
||||
* consult https://forgejo.org/docs/next/contributor/localization/
|
||||
* see the project: https://translate.codeberg.org/projects/forgejo/forgejo/
|
||||
See [locale_readme.md](../locale_readme.md) for modification instructions.
|
||||
|
||||
## Attribution
|
||||
|
||||
|
|
|
@ -20,5 +20,8 @@
|
|||
"themes.names.forgejo-light": "Forgejo – světlé",
|
||||
"themes.names.forgejo-dark": "Forgejo – tmavé",
|
||||
"error.not_found.title": "Stránka nenalezena",
|
||||
"alert.asset_load_failed": "Nepodařilo se načíst soubory příloh z {path}. Ujistěte se, že jsou dané soubory přístupné."
|
||||
"alert.asset_load_failed": "Nepodařilo se načíst soubory příloh z {path}. Ujistěte se, že jsou dané soubory přístupné.",
|
||||
"install.invalid_lfs_path": "Nepodařilo se vytvořit kořen LFS na zvolené cestě: %[1]s",
|
||||
"alert.range_error": " musí být číslo mezi %[1]s a %[2]s.",
|
||||
"meta.last_line": "Díky všem přispěvatelům za pomoc!"
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
"error.not_found.title": "Siden blev ikke fundet",
|
||||
"alert.asset_load_failed": "Kunne ikke indlæse aktivfiler fra {path}. Sørg for, at aktivfilerne er tilgængelige.",
|
||||
"install.invalid_lfs_path": "Kan ikke oprette LFS-roden på den angivne sti: %[1]s",
|
||||
"install.lfs_jwt_secret_failed": "Kan ikke generere en LFS JWT-hemmelighed: %[1]s",
|
||||
"settings.adopt": "Adoptere",
|
||||
"alert.range_error": " skal være et tal mellem %[1]s og %[2]s."
|
||||
"alert.range_error": " skal være et tal mellem %[1]s og %[2]s.",
|
||||
"meta.last_line": "Livet er det dejligste eventyr. - (H.C. Andersen)"
|
||||
}
|
||||
|
|
|
@ -18,5 +18,8 @@
|
|||
"themes.names.forgejo-light": "Forgejo hell",
|
||||
"themes.names.forgejo-dark": "Forgejo dunkel",
|
||||
"error.not_found.title": "Seite nicht gefunden",
|
||||
"alert.asset_load_failed": "Konnte Asset-Dateien nicht von {path} laden. Bitte stelle sicher, dass auf die Asset-Dateien zugegriffen werden kann."
|
||||
"alert.asset_load_failed": "Konnte Asset-Dateien nicht von {path} laden. Bitte stelle sicher, dass auf die Asset-Dateien zugegriffen werden kann.",
|
||||
"install.invalid_lfs_path": "Der LFS-Root konnte nicht am angegebenen Pfad erstellt werden: %[1]s",
|
||||
"alert.range_error": " muss eine Zahl zwischen %[1]s und %[2]s sein.",
|
||||
"meta.last_line": "Vielen Dank für die Übersetzung von Forgejo! Diese Zeile wird von den Benutzern nicht gesehen, dient aber anderen Zwecken im Übersetzungsmanagement. Du kannst eine lustige Tatsache in der Übersetzung platzieren, anstatt sie zu übersetzen."
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
{
|
||||
"repo.pulls.merged_title_desc": "yhdistetty %[1]d committia lähteestä <code>%[2]s</code> kohteeseen <code>%[3]s</code> %[4]s",
|
||||
"repo.pulls.title_desc": "haluaa yhdistää %[1]d committia lähteestä <code>%[2]s</code> kohteeseen <code id=\"%[4]s\">%[3]s</code>",
|
||||
"search.milestone_kind": "Etsi merkkipaaluja...",
|
||||
"search.milestone_kind": "Etsi merkkipaaluja…",
|
||||
"home.welcome.no_activity": "Ei toimintaa",
|
||||
"incorrect_root_url": "Tämä Forgejo-instanssi on määritetty toimimaan osoitteessa \"%s\". Tarkastelet tällä hetkellä Forgejoa eri URL-osoitteen kautta, mikä saattaa aiheuttaa sovelluksen osien toimimattomuutta. Virallinen URL-osoite on Forgejo-ylläpitäjien hallinnoima ROOT_URL-asetus app.ini -tiedostossa.",
|
||||
"themes.names.forgejo-auto": "Forgejo (käyttöjärjestelmän määrittelemä teema)",
|
||||
"home.welcome.activity_hint": "Syötteelläsi ei ole vielä mitään. Toimintasi ja toiminta repositorioissa joita seuraat ilmaantuu tälle sivulle.",
|
||||
"home.explore_repos": "Tutki repositorioita",
|
||||
"home.explore_users": "Tutki käyttäjiä",
|
||||
"home.explore_orgs": "Tutki organisaatioita"
|
||||
"home.explore_orgs": "Tutki organisaatioita",
|
||||
"error.not_found.title": "Sivua ei löytynyt",
|
||||
"themes.names.forgejo-light": "Forgejo, vaalea",
|
||||
"themes.names.forgejo-dark": "Forgejo, tumma",
|
||||
"alert.range_error": " täytyy olla numero välillä %[1]s ja %[2]s."
|
||||
}
|
||||
|
|
|
@ -17,5 +17,9 @@
|
|||
"home.explore_repos": "Tuklasin ang mga repositoryo",
|
||||
"home.explore_users": "Tuklasin ang mga user",
|
||||
"home.explore_orgs": "Tuklasin ang mga organisasyon",
|
||||
"error.not_found.title": "Hindi nahanap ang pahina"
|
||||
"error.not_found.title": "Hindi nahanap ang pahina",
|
||||
"alert.asset_load_failed": "Nabigong i-load ang mga asset file mula sa {path}. Siguraduhin na maa-access ang mga asset file.",
|
||||
"install.invalid_lfs_path": "Nabigong gawin ang LFS root sa tinakdang path: %[1]s",
|
||||
"alert.range_error": " dapat ay numero sa pagitan ng %[1]s at %[2]s.",
|
||||
"meta.last_line": "Sayori... I love you. — MC from Doki Doki Literature Club"
|
||||
}
|
||||
|
|
|
@ -20,5 +20,8 @@
|
|||
"themes.names.forgejo-light": "Forgejo gaišais",
|
||||
"themes.names.forgejo-dark": "Forgejo tumšais",
|
||||
"error.not_found.title": "Lapa nav atrasta",
|
||||
"alert.asset_load_failed": "Neizdevās ielādēt līdzekļu datnes no {path}. Lūgums pārliecināties, ka līdzekļu datnēm var piekļūt."
|
||||
"alert.asset_load_failed": "Neizdevās ielādēt līdzekļu datnes no {path}. Lūgums pārliecināties, ka līdzekļu datnēm var piekļūt.",
|
||||
"install.invalid_lfs_path": "Nav iespējams izveidot LFS pamatmapi norādītajā ceļā: %[1]s",
|
||||
"alert.range_error": " jābūt skaitlin starp %[1]s un %[2]s.",
|
||||
"meta.last_line": "Šis žagaru saišķis nav mans žagaru saišķis."
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
"themes.names.forgejo-auto": "Forgejo (Systeem-Thema nagahn)",
|
||||
"error.not_found.title": "Sied nich funnen",
|
||||
"alert.asset_load_failed": "Kunn Objekt-Dateien ut {path} nich laden. Bidde wees wiss, dat up de Objekt-Dateien togriepen worden kann.",
|
||||
"install.lfs_jwt_secret_failed": "Kunn dat LFS-JWT-Geheemst nich maken: %[1]s",
|
||||
"settings.adopt": "Övernehmen",
|
||||
"install.invalid_lfs_path": "Kunn de LFS-Ruut an de angeven Padd nich maken: %[1]s",
|
||||
"alert.range_error": " mutt eene Tahl tüsken %[1]s un %[2]s wesen."
|
||||
"alert.range_error": " mutt eene Tahl tüsken %[1]s un %[2]s wesen.",
|
||||
"meta.last_line": "Moin!"
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"themes.names.forgejo-dark": "Forgejo donker",
|
||||
"error.not_found.title": "Pagina niet gevonden",
|
||||
"alert.asset_load_failed": "Het laden van asset-bestanden van {path} is mislukt. Controleer of de asset-bestanden toegankelijk zijn.",
|
||||
"settings.adopt": "Adopteer",
|
||||
"install.invalid_lfs_path": "Kan de LFS-root niet aanmaken op het opgegeven pad: %[1]s",
|
||||
"install.lfs_jwt_secret_failed": "Kan geen LFS JWT-geheim genereren: %[1]s"
|
||||
"alert.range_error": " moet een getal zijn tussen %[1]s en %[2]s.",
|
||||
"meta.last_line": "Wist je dat de paprika, behalve in de bekende kleuren rood, geel, oranje en groen, ook in de kleuren wit, paars, lila, muntgroen en bruin voorkomt."
|
||||
}
|
||||
|
|
|
@ -20,5 +20,8 @@
|
|||
"themes.names.forgejo-light": "Forgejo claro",
|
||||
"themes.names.forgejo-dark": "Forgejo escuro",
|
||||
"error.not_found.title": "Página não encontrada",
|
||||
"alert.asset_load_failed": "Não foi possível carregar arquivos de assets de {path}. Por favor, certifique-se que os arquivos podem ser acessados."
|
||||
"alert.asset_load_failed": "Não foi possível carregar arquivos de assets de {path}. Por favor, certifique-se que os arquivos podem ser acessados.",
|
||||
"install.invalid_lfs_path": "Não foi possível criar um root LFS no caminho especificado: %[1]s",
|
||||
"alert.range_error": " deve ser um número entre %[1]s e %[2]s.",
|
||||
"meta.last_line": "real hot girl shit"
|
||||
}
|
||||
|
|
|
@ -19,5 +19,9 @@
|
|||
"themes.names.forgejo-auto": "Forgejo (segue o tema do sistema)",
|
||||
"themes.names.forgejo-light": "Forgejo claro",
|
||||
"themes.names.forgejo-dark": "Forgejo escuro",
|
||||
"error.not_found.title": "Página não encontrada"
|
||||
"error.not_found.title": "Página não encontrada",
|
||||
"alert.asset_load_failed": "Falha ao carregar ficheiros de recurso de {path}. Certifique-se de que os ficheiros de recurso podem ser acedidos.",
|
||||
"install.invalid_lfs_path": "Não foi possível criar a raiz LFS no caminho especificado: %[1]s",
|
||||
"alert.range_error": " deve ser um número entre %[1]s e %[2]s.",
|
||||
"meta.last_line": "Se programarem em Python, por favor usem o uv e Ruff (e estejam atentos ao Red Knot no repositório do Ruff). E vejam também mise-en-place (https://mise.jdx.dev)."
|
||||
}
|
||||
|
|
|
@ -19,5 +19,9 @@
|
|||
"themes.names.forgejo-light": "Forgejo – светлая",
|
||||
"themes.names.forgejo-auto": "Forgejo – как в системе",
|
||||
"themes.names.forgejo-dark": "Forgejo – тёмная",
|
||||
"error.not_found.title": "Страница не найдена"
|
||||
"error.not_found.title": "Страница не найдена",
|
||||
"alert.asset_load_failed": "Не удалось получить ресурсы из {path}. Убедитесь, что файлы ресурсов доступны.",
|
||||
"install.invalid_lfs_path": "Не удалось расположить корень LFS по указанному пути: %[1]s",
|
||||
"alert.range_error": " - число должно быть в диапазоне от %[1]s-%[2]s.",
|
||||
"meta.last_line": "Спасибо Forgejo за такую возможность :3. Интересный факт: мне не удавалось вставить свой вклад в перевод Forgejo и это первый раз, когда у меня это вышло. ❤️."
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
"error.not_found.title": "Сторінку не знайдено",
|
||||
"alert.asset_load_failed": "Не вдалося завантажити файли ресурсів з {path}. Переконайтеся, що до файлів ресурсів є доступ.",
|
||||
"install.invalid_lfs_path": "Не вдалося створити корінь LFS за вказаним шляхом: %[1]s",
|
||||
"settings.adopt": "Прийняти",
|
||||
"install.lfs_jwt_secret_failed": "Не вдалося створити секрет LFS JWT: %[1]s",
|
||||
"alert.range_error": " має бути числом від %[1]s до %[2]s."
|
||||
"alert.range_error": " має бути числом від %[1]s до %[2]s.",
|
||||
"meta.last_line": "Не зливай злий запити на злиття — зіллється зле."
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"themes.names.forgejo-light": "Forgejo 浅色",
|
||||
"themes.names.forgejo-dark": "Forgejo 深色",
|
||||
"error.not_found.title": "页面不存在",
|
||||
"alert.asset_load_failed": "无法从 {path} 加载资源文件。请确保可以访问资源文件。",
|
||||
"install.lfs_jwt_secret_failed": "无法生成 LFS JWT 密钥:%[1]s",
|
||||
"alert.asset_load_failed": "无法从 {path} 加载资源文件。请确保资源文件可被访问。",
|
||||
"install.invalid_lfs_path": "无法在指定路径创建 LFS 根目录:%[1]s",
|
||||
"alert.range_error": " 必须是一个介于 %[1]s 和 %[2]s 之间的数字。"
|
||||
"alert.range_error": " 必须是一个介于 %[1]s 和 %[2]s 之间的数字。",
|
||||
"meta.last_line": "感谢各位对Forgejo翻译的支持和帮助!不需要翻译这个。"
|
||||
}
|
||||
|
|
50
options/locale_readme.md
Normal file
50
options/locale_readme.md
Normal 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
1097
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -16,10 +16,10 @@
|
|||
"@primer/octicons": "19.14.0",
|
||||
"ansi_up": "6.0.5",
|
||||
"asciinema-player": "3.8.2",
|
||||
"chart.js": "4.4.5",
|
||||
"chart.js": "4.4.9",
|
||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||
"chartjs-plugin-zoom": "2.2.0",
|
||||
"clippie": "4.1.1",
|
||||
"clippie": "4.1.6",
|
||||
"css-loader": "7.0.0",
|
||||
"dayjs": "1.11.12",
|
||||
"dropzone": "6.0.0-beta.2",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"htmx.org": "1.9.12",
|
||||
"idiomorph": "0.3.0",
|
||||
"jquery": "3.7.1",
|
||||
"katex": "0.16.21",
|
||||
"katex": "0.16.22",
|
||||
"mermaid": "11.6.0",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"minimatch": "10.0.1",
|
||||
|
@ -55,7 +55,7 @@
|
|||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue3-calendar-heatmap": "2.0.5",
|
||||
"webpack": "5.98.0",
|
||||
"webpack": "5.99.6",
|
||||
"webpack-cli": "6.0.1",
|
||||
"wrap-ansi": "9.0.0"
|
||||
},
|
||||
|
@ -91,6 +91,7 @@
|
|||
"license-checker-rseidelsohn": "4.4.2",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"postcss-html": "1.8.0",
|
||||
"sharp": "0.34.1",
|
||||
"stylelint": "16.17.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.11",
|
||||
|
|
14
poetry.lock
generated
14
poetry.lock
generated
|
@ -130,17 +130,17 @@ six = ">=1.13.0"
|
|||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
description = "A Python implementation of the JSON5 data format."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"},
|
||||
{file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"},
|
||||
{file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"},
|
||||
{file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"]
|
||||
dev = ["build (==1.2.2.post1)", "coverage (==7.5.4)", "coverage (==7.8.0)", "mypy (==1.14.1)", "mypy (==1.15.0)", "pip (==25.0.1)", "pylint (==3.2.7)", "pylint (==3.3.6)", "ruff (==0.11.2)", "twine (==6.1.0)", "uv (==0.6.11)"]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
|
@ -393,13 +393,13 @@ telegram = ["requests"]
|
|||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.13.0"
|
||||
version = "4.13.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"},
|
||||
{file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"},
|
||||
{file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
|
||||
{file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue