mirror of
https://codeberg.org/davrot/forgejo.git
synced 2025-07-22 16:00:03 +02:00
Compare commits
54 commits
cbeb28a0dd
...
f7b3ac4930
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f7b3ac4930 | ||
![]() |
a1bec15043 | ||
![]() |
9a1c10f92e | ||
![]() |
1d3208898f | ||
![]() |
8d80a9cc01 | ||
![]() |
881cdf88bb | ||
![]() |
d3adaf7574 | ||
![]() |
81c960f0c2 | ||
![]() |
580efedad4 | ||
![]() |
e70f48bd44 | ||
![]() |
bd6f3243ab | ||
![]() |
b2b039b6e7 | ||
![]() |
a8e375eb28 | ||
![]() |
4b6ccbd631 | ||
![]() |
4d44ae39e1 | ||
![]() |
db83b43d8d | ||
![]() |
6d7f5fb41e | ||
![]() |
dc2954f8fe | ||
![]() |
7428edacbe | ||
![]() |
10da5e5609 | ||
![]() |
f476ee2196 | ||
![]() |
1242786324 | ||
![]() |
e0bfacac0b | ||
![]() |
b68f923592 | ||
![]() |
82e4ccc223 | ||
![]() |
8f2c08b8dc | ||
![]() |
dcf1eef9e9 | ||
![]() |
249f1fc17e | ||
![]() |
313504739f | ||
![]() |
7df94ff7b2 | ||
![]() |
60adf59620 | ||
![]() |
805e749a15 | ||
![]() |
e4c43c0cec | ||
![]() |
a24ca6e4b4 | ||
![]() |
534020d0ad | ||
![]() |
39d3e874b0 | ||
![]() |
905a5748a8 | ||
![]() |
2529923dea | ||
![]() |
4dd0514022 | ||
![]() |
d17aa98262 | ||
![]() |
6ce9d764bc | ||
![]() |
549fcff997 | ||
![]() |
bc14ad7da2 | ||
![]() |
48671975f1 | ||
![]() |
240d958c0f | ||
![]() |
ea07f0c0f3 | ||
![]() |
e38e761d5b | ||
![]() |
ac1f1a9cfb | ||
![]() |
dbbd0de860 | ||
![]() |
9ecef99ab9 | ||
![]() |
374def9922 | ||
![]() |
3bb6ed8f19 | ||
![]() |
0ed7237b12 | ||
![]() |
fc35915a28 |
185 changed files with 5797 additions and 2172 deletions
|
@ -87,12 +87,24 @@ forgejo.org/modules/eventsource
|
|||
Event.String
|
||||
|
||||
forgejo.org/modules/forgefed
|
||||
NewForgeFollowFromAp
|
||||
NewForgeFollow
|
||||
ForgeFollow.MarshalJSON
|
||||
ForgeFollow.UnmarshalJSON
|
||||
ForgeFollow.Validate
|
||||
NewForgeUndoLike
|
||||
ForgeUndoLike.UnmarshalJSON
|
||||
ForgeUndoLike.Validate
|
||||
NewForgeUserActivityFromAp
|
||||
NewForgeUserActivity
|
||||
ForgeUserActivity.Validate
|
||||
NewPersonIDFromModel
|
||||
GetItemByType
|
||||
JSONUnmarshalerFn
|
||||
NotEmpty
|
||||
NewForgeUserActivityNoteFromAp
|
||||
newNote
|
||||
ForgeUserActivityNote.Validate
|
||||
ToRepository
|
||||
OnRepository
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: data.forgejo.org/renovate/renovate:40.31.0
|
||||
image: data.forgejo.org/renovate/renovate:40.48.4
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
|
|
10
Makefile
10
Makefile
|
@ -37,19 +37,18 @@ endif
|
|||
XGO_VERSION := go-1.21.x
|
||||
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.3.0 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.32.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.1 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go
|
||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@40.31.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@40.48.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
|
||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||
|
@ -924,7 +923,6 @@ deps-tools:
|
|||
$(GO) install $(GOFUMPT_PACKAGE)
|
||||
$(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||
$(GO) install $(GXZ_PACKAGE)
|
||||
$(GO) install $(MISSPELL_PACKAGE)
|
||||
$(GO) install $(SWAGGER_PACKAGE)
|
||||
$(GO) install $(XGO_PACKAGE)
|
||||
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||
|
|
|
@ -18,6 +18,7 @@ func subcmdUser() *cli.Command {
|
|||
microcmdUserDelete(),
|
||||
microcmdUserGenerateAccessToken(),
|
||||
microcmdUserMustChangePassword(),
|
||||
microcmdUserResetMFA(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
73
cmd/admin_user_reset_mfa.go
Normal file
73
cmd/admin_user_reset_mfa.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
user_model "forgejo.org/models/user"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func microcmdUserResetMFA() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "reset-mfa",
|
||||
Usage: "Remove all two-factor authentication configurations for a user",
|
||||
Action: runResetMFA,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
Value: "",
|
||||
Usage: "The user to update",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runResetMFA(ctx context.Context, c *cli.Command) error {
|
||||
if err := argsSet(c, "username"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
webAuthnList, err := auth_model.GetWebAuthnCredentialsByUID(ctx, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, credential := range webAuthnList {
|
||||
if _, err := auth_model.DeleteCredential(ctx, credential.ID, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tfaModes, err := auth_model.GetTwoFactorByUID(ctx, user.ID)
|
||||
if err == nil && tfaModes != nil {
|
||||
if err := auth_model.DeleteTwoFactorByID(ctx, tfaModes.ID, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, is := err.(auth_model.ErrTwoFactorNotEnrolled); !is {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s's two-factor authentication settings have been removed!\n", user.Name)
|
||||
return nil
|
||||
}
|
|
@ -159,7 +159,7 @@ func NewMainApp(version, versionExtra string) *cli.Command {
|
|||
|
||||
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs func() []cli.Flag) *cli.Command {
|
||||
app := &cli.Command{}
|
||||
app.Name = "Forgejo"
|
||||
app.Name = "forgejo"
|
||||
app.Usage = "Beyond coding. We forge."
|
||||
app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
|
||||
app.Version = version + versionExtra
|
||||
|
|
|
@ -408,7 +408,7 @@ local addIssueLabelsOverrides(labels) =
|
|||
regex: '',
|
||||
type: 'query',
|
||||
multi: true,
|
||||
allValue: '.+'
|
||||
allValue: '.+',
|
||||
},
|
||||
)
|
||||
.addTemplate(
|
||||
|
@ -423,7 +423,7 @@ local addIssueLabelsOverrides(labels) =
|
|||
regex: '',
|
||||
type: 'query',
|
||||
multi: true,
|
||||
allValue: '.+'
|
||||
allValue: '.+',
|
||||
},
|
||||
)
|
||||
.addTemplate(
|
||||
|
|
|
@ -183,7 +183,7 @@ RUN_USER = ; git
|
|||
;;
|
||||
;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
|
||||
;; for system SSH this setting has no effect
|
||||
;SSH_SERVER_KEY_EXCHANGES = curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
||||
;SSH_SERVER_KEY_EXCHANGES = mlkem768x25519-sha256, curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
||||
;;
|
||||
;; For the built-in SSH server, choose the MACs to support for SSH connections,
|
||||
;; for system SSH this setting has no effect
|
||||
|
@ -1025,6 +1025,10 @@ LEVEL = Info
|
|||
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||
;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls
|
||||
;;
|
||||
;; Comma separated list of default mirror repo units.
|
||||
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||
;DEFAULT_MIRROR_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.wiki,repo.projects,repo.packages
|
||||
;;
|
||||
;; Prefix archive files by placing them in a directory named after the repository
|
||||
;PREFIX_ARCHIVE_FILES = true
|
||||
;;
|
||||
|
|
26
go.mod
26
go.mod
|
@ -2,10 +2,10 @@ module forgejo.org
|
|||
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.3
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
code.forgejo.org/f3/gof3/v3 v3.10.8
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0
|
||||
code.forgejo.org/forgejo/levelqueue v1.0.0
|
||||
|
@ -17,7 +17,7 @@ require (
|
|||
code.gitea.io/actions-proto-go v0.4.0
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.17.0
|
||||
connectrpc.com/connect v1.18.1
|
||||
github.com/42wim/httpsig v1.2.3
|
||||
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
|
@ -26,7 +26,7 @@ require (
|
|||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2
|
||||
github.com/alecthomas/chroma/v2 v2.18.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.5.1
|
||||
github.com/blevesearch/bleve/v2 v2.5.2
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
|
@ -87,8 +87,8 @@ require (
|
|||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/redis/go-redis/v9 v9.8.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
|
@ -101,13 +101,13 @@ require (
|
|||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go v0.129.0
|
||||
go.uber.org/mock v0.5.2
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/image v0.27.0
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.14.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/text v0.25.0
|
||||
golang.org/x/text v0.26.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
@ -146,7 +146,7 @@ require (
|
|||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.3 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
|
@ -237,9 +237,9 @@ require (
|
|||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
|
48
go.sum
48
go.sum
|
@ -1,7 +1,7 @@
|
|||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
code.forgejo.org/f3/gof3/v3 v3.10.8 h1:cL5XgOcKffqMdKDOqGCXfMc2OBX89xYvGSj2mz3E/VQ=
|
||||
code.forgejo.org/f3/gof3/v3 v3.10.8/go.mod h1:ovgb7R8o7k6poQKQ+KWXHOD9uIoanB6tNSmA3kKOMCI=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFsAE=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
|
||||
code.forgejo.org/forgejo/act v1.26.0 h1:6mTmoaw7d/WpYiw/Pw6AaypxFdgJog5OFi/PMEgEbxs=
|
||||
|
@ -32,8 +32,8 @@ code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
|||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||
connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk=
|
||||
connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
|
||||
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
|
||||
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
|
@ -87,8 +87,8 @@ github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCk
|
|||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/blevesearch/bleve/v2 v2.5.1 h1:cc/O++W2Hcjp1SU5ETHeE+QYWv2oV88ldYEPowdmg8M=
|
||||
github.com/blevesearch/bleve/v2 v2.5.1/go.mod h1:9g/wnbWKm9AgXrU8Ecqi+IDdqjUHWymwkQRDg+5tafU=
|
||||
github.com/blevesearch/bleve/v2 v2.5.2 h1:Ab0r0MODV2C5A6BEL87GqLBySqp/s9xFgceCju6BQk8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.2/go.mod h1:5Dj6dUQxZM6aqYT3eutTD/GpWKGFSsV8f7LDidFbwXo=
|
||||
github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y=
|
||||
github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.2.3 h1:K9/vbGI9ehlXdxjxDRJtoAMt7zGAsMIzc6n8zWcwnhg=
|
||||
|
@ -121,8 +121,8 @@ github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT
|
|||
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.3 h1:7Y0r+a3diEvlazsncexq1qoFOcBd64xwMS7aDm4lo1s=
|
||||
github.com/blevesearch/zapx/v16 v16.2.3/go.mod h1:wVJ+GtURAaRG9KQAMNYyklq0egV+XJlGcXNCE0OFjjA=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
|
@ -499,11 +499,11 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
|||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
|
@ -591,8 +591,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
|||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||
|
@ -603,8 +603,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -620,8 +620,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -633,8 +633,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -684,8 +684,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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -695,8 +695,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
38
manifest.scm
Normal file
38
manifest.scm
Normal file
|
@ -0,0 +1,38 @@
|
|||
;;; Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
;;; SPDX-License-Identifier: MIT
|
||||
;;;
|
||||
;;; Commentary:
|
||||
;;;
|
||||
;;; This is a GNU Guix manifest that can be used to create a
|
||||
;;; development environment to build and test Forgejo.
|
||||
;;;
|
||||
;;; The following is a usage example to create a containerized
|
||||
;;; environment, with HOME shared for the Go cache and the network
|
||||
;;; made available to fetch required Go and Node dependencies.
|
||||
;;;
|
||||
#|
|
||||
guix shell -CNF --share=$HOME -m manifest.scm
|
||||
export GOTOOLCHAIN=local # to use the Go binary from Guix
|
||||
export CC=gcc CGO_ENABLED=1
|
||||
export TAGS="timetzdata sqlite sqlite_unlock_notify"
|
||||
make clean
|
||||
make -j$(nproc)
|
||||
make test -j$(nproc) # run unit tests
|
||||
make test-sqlite -j$(nproc) # run integration tests
|
||||
make watch # run an instance/rebuild on changes
|
||||
|#
|
||||
(specifications->manifest
|
||||
(list "bash-minimal"
|
||||
"coreutils"
|
||||
"findutils"
|
||||
"gcc-toolchain"
|
||||
"git" ;libpcre support is required
|
||||
"git-lfs"
|
||||
"gnupg"
|
||||
"go"
|
||||
"grep"
|
||||
"make"
|
||||
"node"
|
||||
"nss-certs"
|
||||
"openssh"
|
||||
"sed"))
|
|
@ -54,6 +54,8 @@ type FindRunJobOptions struct {
|
|||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
Events []string // []webhook_module.HookEventType
|
||||
RunNumber int64
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||
|
@ -76,5 +78,11 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
|||
if opts.UpdatedBefore > 0 {
|
||||
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
||||
}
|
||||
if len(opts.Events) > 0 {
|
||||
cond = cond.And(builder.In("event", opts.Events))
|
||||
}
|
||||
if opts.RunNumber > 0 {
|
||||
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/shared/types"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/translation"
|
||||
|
@ -353,3 +354,53 @@ func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
|
|||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
func DeleteOfflineRunners(ctx context.Context, olderThan timeutil.TimeStamp, globalOnly bool) error {
|
||||
log.Info("Doing: DeleteOfflineRunners")
|
||||
|
||||
if olderThan.AsTime().After(timeutil.TimeStampNow().AddDuration(-RunnerOfflineTime).AsTime()) {
|
||||
return fmt.Errorf("invalid `cron.cleanup_offline_runners.older_than`value: must be at least %q", RunnerOfflineTime)
|
||||
}
|
||||
|
||||
cond := builder.Or(
|
||||
// never online
|
||||
builder.And(builder.Eq{"last_online": 0}, builder.Lt{"created": olderThan}),
|
||||
// was online but offline
|
||||
builder.And(builder.Gt{"last_online": 0}, builder.Lt{"last_online": olderThan}),
|
||||
)
|
||||
|
||||
if globalOnly {
|
||||
cond = builder.And(cond, builder.Eq{"owner_id": 0}, builder.Eq{"repo_id": 0})
|
||||
}
|
||||
|
||||
if err := db.Iterate(
|
||||
ctx,
|
||||
cond,
|
||||
func(ctx context.Context, r *ActionRunner) error {
|
||||
if err := DeleteRunner(ctx, r); err != nil {
|
||||
return fmt.Errorf("DeleteOfflineRunners: %w", err)
|
||||
}
|
||||
lastOnline := r.LastOnline.AsTime()
|
||||
olderThanTime := olderThan.AsTime()
|
||||
if !lastOnline.IsZero() && lastOnline.Before(olderThanTime) {
|
||||
log.Info(
|
||||
"Deleted runner [ID: %d, Name: %s], last online %s ago",
|
||||
r.ID, r.Name, olderThanTime.Sub(lastOnline).String(),
|
||||
)
|
||||
} else {
|
||||
log.Info(
|
||||
"Deleted runner [ID: %d, Name: %s], unused since %s ago",
|
||||
r.ID, r.Name, olderThanTime.Sub(r.Created.AsTime()).String(),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Finished: DeleteOfflineRunners")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,10 +6,12 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -73,3 +75,68 @@ func TestDeleteRunner(t *testing.T) {
|
|||
idAsBinary[6], idAsBinary[7])
|
||||
assert.Equal(t, idAsHexadecimal, after.UUID[19:])
|
||||
}
|
||||
|
||||
func TestDeleteOfflineRunnersRunnerGlobalOnly(t *testing.T) {
|
||||
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
|
||||
timeutil.MockSet(baseTime)
|
||||
defer timeutil.MockUnset()
|
||||
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
olderThan := timeutil.TimeStampNow().Add(-timeutil.Hour)
|
||||
|
||||
require.NoError(t, DeleteOfflineRunners(db.DefaultContext, olderThan, true))
|
||||
|
||||
// create at test base time
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 12345678})
|
||||
// last_online test base time
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000001})
|
||||
// created one month ago but a repo
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000002})
|
||||
// last online one hour ago
|
||||
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000003})
|
||||
// last online 10 seconds ago
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000004})
|
||||
// created 1 month ago
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000005})
|
||||
// created 1 hour ago
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000006})
|
||||
// last online 1 hour ago
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000007})
|
||||
}
|
||||
|
||||
func TestDeleteOfflineRunnersAll(t *testing.T) {
|
||||
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
|
||||
timeutil.MockSet(baseTime)
|
||||
defer timeutil.MockUnset()
|
||||
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
olderThan := timeutil.TimeStampNow().Add(-timeutil.Hour)
|
||||
|
||||
require.NoError(t, DeleteOfflineRunners(db.DefaultContext, olderThan, false))
|
||||
|
||||
// create at test base time
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 12345678})
|
||||
// last_online test base time
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000001})
|
||||
// created one month ago
|
||||
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000002})
|
||||
// last online one hour ago
|
||||
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000003})
|
||||
// last online 10 seconds ago
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000004})
|
||||
// created 1 month ago
|
||||
unittest.AssertNotExistsBean(t, &ActionRunner{ID: 10000005})
|
||||
// created 1 hour ago
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000006})
|
||||
// last online 1 hour ago
|
||||
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 10000007})
|
||||
}
|
||||
|
||||
func TestDeleteOfflineRunnersErrorOnInvalidOlderThanValue(t *testing.T) {
|
||||
baseTime := time.Date(2024, 5, 19, 7, 40, 32, 0, time.UTC)
|
||||
timeutil.MockSet(baseTime)
|
||||
defer timeutil.MockUnset()
|
||||
require.Error(t, DeleteOfflineRunners(db.DefaultContext, timeutil.TimeStampNow(), false))
|
||||
}
|
||||
|
|
|
@ -34,6 +34,15 @@ var statusNames = map[Status]string{
|
|||
StatusBlocked: "blocked",
|
||||
}
|
||||
|
||||
var nameToStatus = make(map[string]Status, len(statusNames))
|
||||
|
||||
func init() {
|
||||
// Populate name to status lookup map
|
||||
for status, name := range statusNames {
|
||||
nameToStatus[name] = status
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string name of the Status
|
||||
func (s Status) String() string {
|
||||
return statusNames[s]
|
||||
|
@ -102,3 +111,8 @@ func (s Status) AsResult() runnerv1.Result {
|
|||
}
|
||||
return runnerv1.Result_RESULT_UNSPECIFIED
|
||||
}
|
||||
|
||||
func StatusFromString(name string) (Status, bool) {
|
||||
status, exists := nameToStatus[name]
|
||||
return status, exists
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
|
|||
|
||||
// The ssh library can parse the key, so next we find out what key exactly we have.
|
||||
switch pkeyType {
|
||||
case ssh.KeyAlgoDSA:
|
||||
case ssh.KeyAlgoDSA: //nolint:staticcheck
|
||||
rawPub := struct {
|
||||
Name string
|
||||
P, Q, G, Y *big.Int
|
||||
|
|
|
@ -471,3 +471,64 @@
|
|||
need_approval: 0
|
||||
approved_by: 0
|
||||
event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}'
|
||||
|
||||
|
||||
# GET action run(s) test
|
||||
-
|
||||
id: 892
|
||||
title: "successful push run"
|
||||
repo_id: 63
|
||||
owner_id: 2
|
||||
workflow_id: "success.yaml"
|
||||
index: 1
|
||||
trigger_user_id: 2
|
||||
ref: "refs/heads/main"
|
||||
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
|
||||
event: "push"
|
||||
is_fork_pull_request: 0
|
||||
status: 1 # success
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
|
||||
-
|
||||
id: 893
|
||||
title: "failed pull_request run"
|
||||
repo_id: 63
|
||||
owner_id: 2
|
||||
workflow_id: "failed.yaml"
|
||||
index: 2
|
||||
trigger_user_id: 2
|
||||
ref: "refs/heads/bugfix-1"
|
||||
commit_sha: "35c5cddfc19397501ec8f4f7bb808a7c8f04445f"
|
||||
event: "pull_request"
|
||||
is_fork_pull_request: 0
|
||||
status: 2 # failure
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
|
||||
-
|
||||
id: 894
|
||||
title: "running workflow_dispatch run"
|
||||
repo_id: 63
|
||||
owner_id: 2
|
||||
workflow_id: "running.yaml"
|
||||
index: 3
|
||||
trigger_user_id: 2
|
||||
ref: "refs/heads/main"
|
||||
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
|
||||
event: "workflow_dispatch"
|
||||
is_fork_pull_request: 0
|
||||
status: 6 # running
|
||||
started: 1683636528
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
|
|
|
@ -18,3 +18,122 @@
|
|||
created: 1716104432
|
||||
updated: 1716104432
|
||||
deleted: ~
|
||||
- id: 10000001
|
||||
uuid: 10d3b248-6460-4bf5-b819-1f5b3109e10f
|
||||
name: global-online
|
||||
version: v6.3.1+7-gc4c0ca0
|
||||
owner_id: 0
|
||||
repo_id: 0
|
||||
description: ""
|
||||
base: 0
|
||||
repo_range: ""
|
||||
token_hash: 7e9ed71f64e98ce1f70e94c63f3cb6c41a8cb0b90de3e1daf7ec5c35361d60ed44da67c5ac393b7aaf443dcfc766007dc828
|
||||
token_salt: WUcgZWl7mW
|
||||
last_online: 1716104422
|
||||
last_active: 0
|
||||
agent_labels: '["docker"]'
|
||||
created: 1716104431
|
||||
updated: 1716104422
|
||||
deleted: ~
|
||||
- id: 10000002
|
||||
uuid: 1d188484-dd97-4a70-b707-5e87b578ab6b
|
||||
name: repo-never-used
|
||||
version: v6.3.1+7-gc4c0ca0
|
||||
owner_id: 0
|
||||
repo_id: 1
|
||||
description: ""
|
||||
base: 0
|
||||
repo_range: ""
|
||||
token_hash: 51e88c17ac8b54dd101dc2e4f530a71643c703adba7170f4b1a28f1cb483b4cfb107798c521e0532ef3c6480b64518a5c6a5
|
||||
token_salt: 4rh8ncXYIO
|
||||
last_online: 0
|
||||
last_active: 0
|
||||
agent_labels: '["docker"]'
|
||||
created: 1713512432
|
||||
updated: 1713512432
|
||||
deleted: ~
|
||||
- id: 10000003
|
||||
uuid: 7a039c6b-b0b2-4cf5-a93d-715d617f99e2
|
||||
name: global-offline
|
||||
version: v6.3.1+7-gc4c0ca0
|
||||
owner_id: 0
|
||||
repo_id: 0
|
||||
description: ""
|
||||
base: 0
|
||||
repo_range: ""
|
||||
token_hash: c76960c56bc6069f0d1648991ec626500abe8c15286f5c355d565c3b5ba945d7d6f1272a6c77849e592528179511b94f5d69
|
||||
token_salt: TFMe2jhOkB
|
||||
last_online: 1715499632
|
||||
last_active: 0
|
||||
agent_labels: '["docker"]'
|
||||
created: 1715499632
|
||||
updated: 1715499632
|
||||
deleted: ~
|
||||
- id: 10000004
|
||||
uuid: 93ca7fdd-faca-4df6-a474-8345263ef10b
|
||||
name: user-online
|
||||
version: v6.3.1+7-gc4c0ca0
|
||||
owner_id: 1
|
||||
repo_id: 0
|
||||
description: ""
|
||||
base: 0
|
||||
repo_range: ""
|
||||
token_hash: 6ddf7f0f2301d2b3f66418145dc497a6d09fa6586e659afcb5ae2a0c5b639561d795aff8062537db9df73b396842ea826134
|
||||
token_salt: QcdGuReAp4
|
||||
last_online: 1716104422
|
||||
last_active: 0
|
||||
agent_labels: '["docker"]'
|
||||
created: 1716104431
|
||||
updated: 1716104422
|
||||
deleted: ~
|
||||
- id: 10000005
|
||||
uuid: a8534df6-c4be-40f4-9714-903b69d973d9
|
||||
name: user-never-used
|
||||
version: v6.3.1+7-gc4c0ca0
|
||||
owner_id: 1
|
||||
repo_id: 0
|
||||
description: desc
|
||||
base: 0
|
||||
repo_range: ""
|
||||
token_hash: 4441de7defcfc3d21baa608dec66a562cf23307abddaabdbb836907ac5f48c8780c354891916c525b79ec7af8e95be7a09b4
|
||||
token_salt: ONNqIOnj3t
|
||||
last_online: 0
|
||||
last_active: 0
|
||||
agent_labels: '["docker"]'
|
||||
created: 1713512433
|
||||
updated: 1713512433
|
||||
deleted: ~
|
||||
- id: 10000006
|
||||
uuid: e1c5bb6c-de68-4335-8955-5192f76708ac
|
||||
name: orga-fresh-created
|
||||
version: v6.3.1+7-gc4c0ca0
|
||||
owner_id: 35
|
||||
repo_id: 0
|
||||
description: ""
|
||||
base: 0
|
||||
repo_range: ""
|
||||
token_hash: a61f9ee48c6847d243ace0a8936efe80af9277c7bc46d6da6e03d1d406608b8023ee66600ad24f0effaa8e3338f92ac97ac9
|
||||
token_salt: fZJKjrFGWA
|
||||
last_online: 0
|
||||
last_active: 0
|
||||
agent_labels: '["docker"]'
|
||||
created: 1716100832
|
||||
updated: 1716100832
|
||||
deleted: ~
|
||||
- id: 10000007
|
||||
uuid: ff755f06-948e-479b-8031-5b3e9f123e32
|
||||
name: orga-offline
|
||||
version: v6.3.1+7-gc4c0ca0
|
||||
owner_id: 35
|
||||
repo_id: 0
|
||||
description: ""
|
||||
base: 0
|
||||
repo_range: ""
|
||||
token_hash: 9372efb38f9b64efe65065380abe2f24ef34a59d9619f4cdc08f1151e9849f0b6e722aa10538e8730288de6e2f09acdac695
|
||||
token_salt: TnU7iiIdCb
|
||||
last_online: 1716100832
|
||||
last_active: 0
|
||||
agent_labels: '["docker"]'
|
||||
created: 1736085520
|
||||
updated: 1716100832
|
||||
deleted: ~
|
||||
|
|
|
@ -795,3 +795,10 @@
|
|||
type: 10
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 115
|
||||
repo_id: 63
|
||||
type: 10
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
|
|
@ -1889,3 +1889,35 @@
|
|||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
topics: '[]'
|
||||
|
||||
-
|
||||
id: 63
|
||||
owner_id: 2
|
||||
owner_name: user2
|
||||
lower_name: test_action_run_search
|
||||
name: test_action_run_search
|
||||
default_branch: main
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
num_milestones: 0
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
num_closed_projects: 0
|
||||
is_private: true
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_mirror: false
|
||||
status: 0
|
||||
is_fork: false
|
||||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 0
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
topics: '[]'
|
||||
|
|
|
@ -239,3 +239,15 @@
|
|||
num_members: 2
|
||||
includes_all_repositories: false
|
||||
can_create_org_repo: false
|
||||
|
||||
-
|
||||
id: 25
|
||||
org_id: 17
|
||||
lower_name: super-user
|
||||
name: super-user
|
||||
description: ""
|
||||
authorize: 3
|
||||
num_repos: 0
|
||||
num_members: 0
|
||||
includes_all_repositories: 0
|
||||
can_create_org_repo: 0
|
||||
|
|
|
@ -329,3 +329,10 @@
|
|||
team_id: 22
|
||||
type: 3
|
||||
access_mode: 1
|
||||
|
||||
-
|
||||
id: 84
|
||||
org_id: 17
|
||||
team_id: 25
|
||||
type: 3
|
||||
access_mode: 3
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
num_followers: 2
|
||||
num_following: 1
|
||||
num_stars: 2
|
||||
num_repos: 17
|
||||
num_repos: 18
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
|
@ -642,7 +642,7 @@
|
|||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 2
|
||||
num_teams: 3
|
||||
num_teams: 4
|
||||
num_members: 4
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
|
|
|
@ -10,16 +10,45 @@ import (
|
|||
|
||||
func SetTopicsAsEmptySlice(x *xorm.Engine) error {
|
||||
var err error
|
||||
if x.Dialect().URI().DBType == schemas.POSTGRES {
|
||||
switch x.Dialect().URI().DBType {
|
||||
case schemas.MYSQL:
|
||||
_, err = x.Exec("UPDATE `repository` SET topics = '[]' WHERE topics IS NULL OR topics = 'null'")
|
||||
case schemas.SQLITE:
|
||||
_, err = x.Exec("UPDATE `repository` SET topics = '[]' WHERE topics IS NULL OR topics = 'null'")
|
||||
case schemas.POSTGRES:
|
||||
_, err = x.Exec("UPDATE `repository` SET topics = '[]' WHERE topics IS NULL OR topics::text = 'null'")
|
||||
} else {
|
||||
_, err = x.Exec("UPDATE `repository` SET topics = '[]' WHERE topics IS NULL")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if x.Dialect().URI().DBType == schemas.SQLITE {
|
||||
sessMigration := x.NewSession()
|
||||
defer sessMigration.Close()
|
||||
if err := sessMigration.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sessMigration.Exec("ALTER TABLE `repository` RENAME COLUMN `topics` TO `topics_backup`")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sessMigration.Exec("ALTER TABLE `repository` ADD COLUMN `topics` TEXT NOT NULL DEFAULT '[]'")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sessMigration.Exec("UPDATE `repository` SET `topics` = `topics_backup`")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sessMigration.Exec("ALTER TABLE `repository` DROP COLUMN `topics_backup`")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sessMigration.Commit()
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Topics []string `xorm:"TEXT JSON NOT NULL"`
|
||||
|
|
5
models/organization/TestFindOrgs/org_user.yml
Normal file
5
models/organization/TestFindOrgs/org_user.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
-
|
||||
id: 1000
|
||||
uid: 4
|
||||
org_id: 22
|
||||
is_public: true
|
|
@ -26,6 +26,7 @@ type SearchOrganizationsOptions struct {
|
|||
type FindOrgOptions struct {
|
||||
db.ListOptions
|
||||
UserID int64
|
||||
IncludeLimited bool
|
||||
IncludePrivate bool
|
||||
}
|
||||
|
||||
|
@ -43,7 +44,11 @@ func (opts FindOrgOptions) ToConds() builder.Cond {
|
|||
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
|
||||
}
|
||||
if !opts.IncludePrivate {
|
||||
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
||||
if !opts.IncludeLimited {
|
||||
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
||||
} else {
|
||||
cond = cond.And(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
|
||||
}
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ func TestCountOrganizations(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFindOrgs(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/organization/TestFindOrgs")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||
|
@ -34,8 +35,14 @@ func TestFindOrgs(t *testing.T) {
|
|||
IncludePrivate: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if assert.Len(t, orgs, 1) {
|
||||
assert.EqualValues(t, 3, orgs[0].ID)
|
||||
if assert.Len(t, orgs, 2) {
|
||||
if orgs[0].ID == 22 {
|
||||
assert.EqualValues(t, 22, orgs[0].ID)
|
||||
assert.EqualValues(t, 3, orgs[1].ID)
|
||||
} else {
|
||||
assert.EqualValues(t, 3, orgs[0].ID)
|
||||
assert.EqualValues(t, 22, orgs[1].ID)
|
||||
}
|
||||
}
|
||||
|
||||
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||
|
@ -50,6 +57,14 @@ func TestFindOrgs(t *testing.T) {
|
|||
IncludePrivate: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 2, total)
|
||||
|
||||
total, err = db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||
UserID: 4,
|
||||
IncludePrivate: false,
|
||||
IncludeLimited: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 1, total)
|
||||
}
|
||||
|
||||
|
|
|
@ -235,7 +235,7 @@ func UpdateAttachmentByUUID(ctx context.Context, attach *Attachment, cols ...str
|
|||
if attach.UUID == "" {
|
||||
return errors.New("attachment uuid should be not blank")
|
||||
}
|
||||
if attach.ExternalURL != "" && !validation.IsValidExternalURL(attach.ExternalURL) {
|
||||
if attach.ExternalURL != "" && !validation.IsValidReleaseAssetURL(attach.ExternalURL) {
|
||||
return ErrInvalidExternalURL{ExternalURL: attach.ExternalURL}
|
||||
}
|
||||
_, err := db.GetEngine(ctx).Where("uuid=?", attach.UUID).Cols(cols...).Update(attach)
|
||||
|
@ -244,7 +244,7 @@ func UpdateAttachmentByUUID(ctx context.Context, attach *Attachment, cols ...str
|
|||
|
||||
// UpdateAttachment updates the given attachment in database
|
||||
func UpdateAttachment(ctx context.Context, atta *Attachment) error {
|
||||
if atta.ExternalURL != "" && !validation.IsValidExternalURL(atta.ExternalURL) {
|
||||
if atta.ExternalURL != "" && !validation.IsValidReleaseAssetURL(atta.ExternalURL) {
|
||||
return ErrInvalidExternalURL{ExternalURL: atta.ExternalURL}
|
||||
}
|
||||
sess := db.GetEngine(ctx).Cols("name", "issue_id", "release_id", "comment_id", "download_count")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package unit
|
||||
|
@ -69,7 +70,7 @@ func (u Type) LogString() string {
|
|||
}
|
||||
|
||||
var (
|
||||
// AllRepoUnitTypes contains all the unit types
|
||||
// AllRepoUnitTypes contains all units
|
||||
AllRepoUnitTypes = []Type{
|
||||
TypeCode,
|
||||
TypeIssues,
|
||||
|
@ -83,7 +84,7 @@ var (
|
|||
TypeActions,
|
||||
}
|
||||
|
||||
// DefaultRepoUnits contains the default unit types
|
||||
// DefaultRepoUnits contains default units for regular repos
|
||||
DefaultRepoUnits = []Type{
|
||||
TypeCode,
|
||||
TypeIssues,
|
||||
|
@ -95,12 +96,22 @@ var (
|
|||
TypeActions,
|
||||
}
|
||||
|
||||
// ForkRepoUnits contains the default unit types for forks
|
||||
// ForkRepoUnits contains default units for forks
|
||||
DefaultForkRepoUnits = []Type{
|
||||
TypeCode,
|
||||
TypePullRequests,
|
||||
}
|
||||
|
||||
// DefaultMirrorRepoUnits contains default units for mirrors
|
||||
DefaultMirrorRepoUnits = []Type{
|
||||
TypeCode,
|
||||
TypeIssues,
|
||||
TypeReleases,
|
||||
TypeWiki,
|
||||
TypeProjects,
|
||||
TypePackages,
|
||||
}
|
||||
|
||||
// NotAllowedDefaultRepoUnits contains units that can't be default
|
||||
NotAllowedDefaultRepoUnits = []Type{
|
||||
TypeExternalWiki,
|
||||
|
@ -172,6 +183,8 @@ func LoadUnitConfig() error {
|
|||
if len(DefaultRepoUnits) == 0 {
|
||||
return errors.New("no default repository units found")
|
||||
}
|
||||
|
||||
// Default fork repo units
|
||||
setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...)
|
||||
if len(invalidKeys) > 0 {
|
||||
log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", "))
|
||||
|
@ -181,6 +194,16 @@ func LoadUnitConfig() error {
|
|||
return errors.New("no default fork repository units found")
|
||||
}
|
||||
|
||||
// Default mirror repo units
|
||||
setDefaultMirrorRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultMirrorRepoUnits...)
|
||||
if len(invalidKeys) > 0 {
|
||||
log.Warn("Invalid keys in default mirror repo units: %s", strings.Join(invalidKeys, ", "))
|
||||
}
|
||||
DefaultMirrorRepoUnits = validateDefaultRepoUnits(DefaultMirrorRepoUnits, setDefaultMirrorRepoUnits)
|
||||
if len(DefaultMirrorRepoUnits) == 0 {
|
||||
return errors.New("no default mirror repository units found")
|
||||
}
|
||||
|
||||
// Collect the allowed repo unit groups. Mutually exclusive units are
|
||||
// grouped together.
|
||||
AllowedRepoUnitGroups = [][]Type{}
|
||||
|
|
|
@ -299,6 +299,24 @@ func (w *Webhook) HasPackageEvent() bool {
|
|||
(w.ChooseEvents && w.Package)
|
||||
}
|
||||
|
||||
// HasActionRunFailureEvent returns if hook enabled action failure event.
|
||||
func (w *Webhook) HasActionRunFailureEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.ActionRunFailure)
|
||||
}
|
||||
|
||||
// HasActionRunRecoverEvent returns if hook enabled action recover event.
|
||||
func (w *Webhook) HasActionRunRecoverEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.ActionRunRecover)
|
||||
}
|
||||
|
||||
// HasActionRunSuccessEvent returns if hook enabled action success event.
|
||||
func (w *Webhook) HasActionRunSuccessEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.ActionRunSuccess)
|
||||
}
|
||||
|
||||
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
|
||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
|
||||
return w.SendEverything ||
|
||||
|
@ -337,6 +355,9 @@ func (w *Webhook) EventCheckers() []struct {
|
|||
{w.HasReleaseEvent, webhook_module.HookEventRelease},
|
||||
{w.HasPackageEvent, webhook_module.HookEventPackage},
|
||||
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
|
||||
{w.HasActionRunFailureEvent, webhook_module.HookEventActionRunFailure},
|
||||
{w.HasActionRunRecoverEvent, webhook_module.HookEventActionRunRecover},
|
||||
{w.HasActionRunSuccessEvent, webhook_module.HookEventActionRunSuccess},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,8 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
||||
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
||||
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
|
||||
"package", "pull_request_review_request",
|
||||
"package", "pull_request_review_request", "action_run_failure",
|
||||
"action_run_recover", "action_run_success",
|
||||
},
|
||||
(&Webhook{
|
||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||
|
@ -89,15 +90,78 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateWebhook(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
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}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
unittest.AssertExistsAndLoadBean(t, hook)
|
||||
t.Run("Some chosen events 1", func(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":true,"events":{"create":false,"push":true,"pull_request":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
hookFromDb := unittest.AssertExistsAndLoadBean(t, hook)
|
||||
assert.Equal(t, []string{
|
||||
string(webhook_module.HookEventPush),
|
||||
string(webhook_module.HookEventPullRequest),
|
||||
}, hookFromDb.EventsArray())
|
||||
})
|
||||
|
||||
t.Run("Some chosen events 2", func(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":true,"events":{"action_run_recover":false,"action_run_success":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
hookFromDb := unittest.AssertExistsAndLoadBean(t, hook)
|
||||
assert.Equal(t, []string{string(webhook_module.HookEventActionRunSuccess)}, hookFromDb.EventsArray())
|
||||
})
|
||||
|
||||
t.Run("All events", func(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":true,"events":{"create":true,"delete":true,"fork":true,"issues":true,"issue_assign":true,"issue_label":true,"issue_milestone":true,"issue_comment":true,"push":true,"pull_request":true,"pull_request_assign":true,"pull_request_label":true,"pull_request_milestone":true,"pull_request_comment":true,"pull_request_review":true,"pull_request_sync":true,"pull_request_review_request":true,"wiki":true,"repository":true,"release":true,"package":true,"action_run_failure":true,"action_run_recover":true,"action_run_success":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
hookFromDb := unittest.AssertExistsAndLoadBean(t, hook)
|
||||
assert.Equal(t, []string{
|
||||
string(webhook_module.HookEventCreate),
|
||||
string(webhook_module.HookEventDelete),
|
||||
string(webhook_module.HookEventFork),
|
||||
string(webhook_module.HookEventPush),
|
||||
string(webhook_module.HookEventIssues),
|
||||
string(webhook_module.HookEventIssueAssign),
|
||||
string(webhook_module.HookEventIssueLabel),
|
||||
string(webhook_module.HookEventIssueMilestone),
|
||||
string(webhook_module.HookEventIssueComment),
|
||||
string(webhook_module.HookEventPullRequest),
|
||||
string(webhook_module.HookEventPullRequestAssign),
|
||||
string(webhook_module.HookEventPullRequestLabel),
|
||||
string(webhook_module.HookEventPullRequestMilestone),
|
||||
string(webhook_module.HookEventPullRequestComment),
|
||||
string(webhook_module.HookEventPullRequestReviewApproved),
|
||||
string(webhook_module.HookEventPullRequestReviewRejected),
|
||||
string(webhook_module.HookEventPullRequestReviewComment),
|
||||
string(webhook_module.HookEventPullRequestSync),
|
||||
string(webhook_module.HookEventWiki),
|
||||
string(webhook_module.HookEventRepository),
|
||||
string(webhook_module.HookEventRelease),
|
||||
string(webhook_module.HookEventPackage),
|
||||
string(webhook_module.HookEventPullRequestReviewRequest),
|
||||
// these aren't webhook event types
|
||||
// string(webhook_module.HookEventSchedule),
|
||||
// string(webhook_module.HookEventWorkflowDispatch),
|
||||
string(webhook_module.HookEventActionRunFailure),
|
||||
string(webhook_module.HookEventActionRunRecover),
|
||||
string(webhook_module.HookEventActionRunSuccess),
|
||||
},
|
||||
hookFromDb.EventsArray())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetWebhookByRepoID(t *testing.T) {
|
||||
|
|
57
modules/forgefed/activity_follow.go
Normal file
57
modules/forgefed/activity_follow.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ForgeFollow activity data type
|
||||
// swagger:model
|
||||
type ForgeFollow struct {
|
||||
// swagger:ignore
|
||||
ap.Activity
|
||||
}
|
||||
|
||||
func NewForgeFollowFromAp(activity ap.Activity) (ForgeFollow, error) {
|
||||
result := ForgeFollow{}
|
||||
result.Activity = activity
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return ForgeFollow{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewForgeFollow(actor, object string) (ForgeFollow, error) {
|
||||
result := ForgeFollow{}
|
||||
result.Type = ap.FollowType
|
||||
result.ID = ap.IRI(actor + "/follows/" + uuid.New().String())
|
||||
result.Actor = ap.IRI(actor)
|
||||
result.Object = ap.IRI(object)
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return ForgeFollow{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (follow ForgeFollow) MarshalJSON() ([]byte, error) {
|
||||
return follow.Activity.MarshalJSON()
|
||||
}
|
||||
|
||||
func (follow *ForgeFollow) UnmarshalJSON(data []byte) error {
|
||||
return follow.Activity.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
func (follow ForgeFollow) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(string(follow.Type), "type")...)
|
||||
result = append(result, validation.ValidateOneOf(string(follow.Type), []any{"Follow"}, "type")...)
|
||||
result = append(result, validation.ValidateIDExists(follow.Actor, "actor")...)
|
||||
result = append(result, validation.ValidateIDExists(follow.Object, "object")...)
|
||||
|
||||
return result
|
||||
}
|
31
modules/forgefed/activity_follow_test.go
Normal file
31
modules/forgefed/activity_follow_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func Test_NewForgeFollowValidation(t *testing.T) {
|
||||
sut := ForgeFollow{}
|
||||
sut.Type = "Follow"
|
||||
sut.Actor = ap.IRI("example.org/alice")
|
||||
sut.Object = ap.IRI("example.org/bob")
|
||||
|
||||
if err, _ := validation.IsValid(sut); !err {
|
||||
t.Errorf("sut is invalid: %v\n", err)
|
||||
}
|
||||
|
||||
sut = ForgeFollow{}
|
||||
sut.Actor = ap.IRI("example.org/alice")
|
||||
sut.Object = ap.IRI("example.org/bob")
|
||||
|
||||
if err, _ := validation.IsValid(sut); err {
|
||||
t.Errorf("sut is valid: %v\n", err)
|
||||
}
|
||||
}
|
77
modules/forgefed/activity_user_activity.go
Normal file
77
modules/forgefed/activity_user_activity.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// ForgeFollow activity data type
|
||||
// swagger:model
|
||||
type ForgeUserActivity struct {
|
||||
ap.Activity
|
||||
Note ForgeUserActivityNote
|
||||
}
|
||||
|
||||
func NewForgeUserActivityFromAp(activity ap.Activity) (ForgeUserActivity, error) {
|
||||
result := ForgeUserActivity{}
|
||||
result.Activity = activity
|
||||
note, err := NewForgeUserActivityNoteFromAp(activity.Object)
|
||||
if err != nil {
|
||||
return ForgeUserActivity{}, err
|
||||
}
|
||||
result.Note = note
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return ForgeUserActivity{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewForgeUserActivity(doer *user_model.User, actionID int64, content string) (ForgeUserActivity, error) {
|
||||
id := fmt.Sprintf("%s/activities/%d", doer.APActorID(), actionID)
|
||||
published := time.Now()
|
||||
|
||||
result := ForgeUserActivity{}
|
||||
result.ID = ap.IRI(id + "/activity")
|
||||
result.Type = ap.CreateType
|
||||
result.Actor = ap.IRI(doer.APActorID())
|
||||
result.Published = published
|
||||
result.To = ap.ItemCollection{
|
||||
ap.IRI("https://www.w3.org/ns/activitystreams#Public"),
|
||||
}
|
||||
result.CC = ap.ItemCollection{
|
||||
ap.IRI(doer.APActorID() + "/followers"),
|
||||
}
|
||||
note, err := newNote(doer, content, id, published)
|
||||
if err != nil {
|
||||
return ForgeUserActivity{}, err
|
||||
}
|
||||
result.Object = note
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (userActivity ForgeUserActivity) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(string(userActivity.Type), "type")...)
|
||||
result = append(result, validation.ValidateOneOf(string(userActivity.Type), []any{"Create"}, "type")...)
|
||||
result = append(result, validation.ValidateIDExists(userActivity.Actor, "actor")...)
|
||||
|
||||
if len(userActivity.To) == 0 {
|
||||
result = append(result, "Missing to")
|
||||
}
|
||||
if len(userActivity.CC) == 0 {
|
||||
result = append(result, "Missing cc")
|
||||
}
|
||||
|
||||
result = append(result, userActivity.Note.Validate()...)
|
||||
|
||||
return result
|
||||
}
|
40
modules/forgefed/activity_user_activity_test.go
Normal file
40
modules/forgefed/activity_user_activity_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func Test_ForgeUserActivityValidation(t *testing.T) {
|
||||
note := ForgeUserActivityNote{}
|
||||
note.Type = "Note"
|
||||
note.Content = ap.NaturalLanguageValues{
|
||||
{
|
||||
Ref: ap.NilLangRef,
|
||||
Value: ap.Content("Any Content!"),
|
||||
},
|
||||
}
|
||||
note.URL = ap.IRI("example.org/user-id/57")
|
||||
|
||||
sut := ForgeUserActivity{}
|
||||
sut.Type = "Create"
|
||||
sut.Actor = ap.IRI("example.org/user-id/23")
|
||||
sut.CC = ap.ItemCollection{
|
||||
ap.IRI("example.org/registration/public#2nd"),
|
||||
}
|
||||
sut.To = ap.ItemCollection{
|
||||
ap.IRI("example.org/registration/public"),
|
||||
}
|
||||
|
||||
sut.Note = note
|
||||
|
||||
if res, _ := validation.IsValid(sut); !res {
|
||||
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
|
||||
}
|
||||
}
|
|
@ -10,8 +10,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// ----------------------------- ActorID --------------------------------------------
|
||||
|
@ -41,12 +39,18 @@ func NewActorID(uri string) (ActorID, error) {
|
|||
}
|
||||
|
||||
func (id ActorID) AsURI() string {
|
||||
var result string
|
||||
var result, path string
|
||||
|
||||
if id.Path == "" {
|
||||
path = id.ID
|
||||
} else {
|
||||
path = fmt.Sprintf("%s/%s", id.Path, id.ID)
|
||||
}
|
||||
|
||||
if id.IsPortSupplemented {
|
||||
result = fmt.Sprintf("%s://%s/%s/%s", id.HostSchema, id.Host, id.Path, id.ID)
|
||||
result = fmt.Sprintf("%s://%s/%s", id.HostSchema, id.Host, path)
|
||||
} else {
|
||||
result = fmt.Sprintf("%s://%s:%d/%s/%s", id.HostSchema, id.Host, id.HostPort, id.Path, id.ID)
|
||||
result = fmt.Sprintf("%s://%s:%d/%s", id.HostSchema, id.Host, id.HostPort, path)
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -54,8 +58,7 @@ func (id ActorID) AsURI() string {
|
|||
|
||||
func (id ActorID) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(id.ID, "userId")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.ID, "ID")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.Host, "host")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.HostPort, "hostPort")...)
|
||||
result = append(result, validation.ValidateNotEmpty(id.HostSchema, "hostSchema")...)
|
||||
|
@ -68,115 +71,6 @@ func (id ActorID) Validate() []string {
|
|||
return result
|
||||
}
|
||||
|
||||
// ----------------------------- PersonID --------------------------------------------
|
||||
type PersonID struct {
|
||||
ActorID
|
||||
}
|
||||
|
||||
// Factory function for PersonID. Created struct is asserted to be valid
|
||||
func NewPersonID(uri, source string) (PersonID, error) {
|
||||
result, err := newActorID(uri)
|
||||
if err != nil {
|
||||
return PersonID{}, err
|
||||
}
|
||||
result.Source = source
|
||||
|
||||
// validate Person specific path
|
||||
personID := PersonID{result}
|
||||
if valid, err := validation.IsValid(personID); !valid {
|
||||
return PersonID{}, err
|
||||
}
|
||||
|
||||
return personID, nil
|
||||
}
|
||||
|
||||
func (id PersonID) AsWebfinger() string {
|
||||
result := fmt.Sprintf("@%s@%s", strings.ToLower(id.ID), strings.ToLower(id.Host))
|
||||
return result
|
||||
}
|
||||
|
||||
func (id PersonID) AsLoginName() string {
|
||||
result := fmt.Sprintf("%s%s", strings.ToLower(id.ID), id.HostSuffix())
|
||||
return result
|
||||
}
|
||||
|
||||
func (id PersonID) HostSuffix() string {
|
||||
result := fmt.Sprintf("-%s", strings.ToLower(id.Host))
|
||||
return result
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ----------------------------- RepositoryID --------------------------------------------
|
||||
|
||||
type RepositoryID struct {
|
||||
ActorID
|
||||
}
|
||||
|
||||
// Factory function for RepositoryID. Created struct is asserted to be valid.
|
||||
func NewRepositoryID(uri, source string) (RepositoryID, error) {
|
||||
result, err := newActorID(uri)
|
||||
if err != nil {
|
||||
return RepositoryID{}, err
|
||||
}
|
||||
result.Source = source
|
||||
|
||||
// validate Person specific
|
||||
repoID := RepositoryID{result}
|
||||
if valid, err := validation.IsValid(repoID); !valid {
|
||||
return RepositoryID{}, err
|
||||
}
|
||||
|
||||
return repoID, nil
|
||||
}
|
||||
|
||||
func (id RepositoryID) 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/repository-id" && strings.ToLower(id.Path) != "api/activitypub/repository-id" {
|
||||
result = append(result, fmt.Sprintf("path: %q has to be a repo specific api path", id.Path))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func containsEmptyString(ar []string) bool {
|
||||
for _, elem := range ar {
|
||||
if elem == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func removeEmptyStrings(ls []string) []string {
|
||||
var rs []string
|
||||
for _, str := range ls {
|
||||
if str != "" {
|
||||
rs = append(rs, str)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// ----------------------------- newActorID --------------------------------------------
|
||||
|
||||
func newActorID(uri string) (ActorID, error) {
|
||||
validatedURI, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
|
@ -212,28 +106,21 @@ func newActorID(uri string) (ActorID, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// ----------------------------- ForgePerson -------------------------------------
|
||||
|
||||
// ForgePerson activity data type
|
||||
// swagger:model
|
||||
type ForgePerson struct {
|
||||
// swagger:ignore
|
||||
ap.Actor
|
||||
func containsEmptyString(ar []string) bool {
|
||||
for _, elem := range ar {
|
||||
if elem == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s ForgePerson) MarshalJSON() ([]byte, error) {
|
||||
return s.Actor.MarshalJSON()
|
||||
}
|
||||
|
||||
func (s *ForgePerson) UnmarshalJSON(data []byte) error {
|
||||
return s.Actor.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
func (s ForgePerson) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(string(s.Type), "Type")...)
|
||||
result = append(result, validation.ValidateOneOf(string(s.Type), []any{string(ap.PersonType)}, "Type")...)
|
||||
result = append(result, validation.ValidateNotEmpty(s.PreferredUsername.String(), "PreferredUsername")...)
|
||||
|
||||
return result
|
||||
func removeEmptyStrings(ls []string) []string {
|
||||
var rs []string
|
||||
for _, str := range ls {
|
||||
if str != "" {
|
||||
rs = append(rs, str)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
|
122
modules/forgefed/actor_person.go
Normal file
122
modules/forgefed/actor_person.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// ----------------------------- PersonID --------------------------------------------
|
||||
type PersonID struct {
|
||||
ActorID
|
||||
}
|
||||
|
||||
const (
|
||||
personIDapiPathV1 = "api/v1/activitypub/user-id"
|
||||
personIDapiPathV1Latest = "api/activitypub/user-id"
|
||||
)
|
||||
|
||||
// Factory function for PersonID. Created struct is asserted to be valid
|
||||
func NewPersonID(uri, source string) (PersonID, error) {
|
||||
result, err := newActorID(uri)
|
||||
if err != nil {
|
||||
return PersonID{}, err
|
||||
}
|
||||
result.Source = source
|
||||
|
||||
// validate Person specific path
|
||||
personID := PersonID{result}
|
||||
if valid, err := validation.IsValid(personID); !valid {
|
||||
return PersonID{}, err
|
||||
}
|
||||
|
||||
return personID, nil
|
||||
}
|
||||
|
||||
func NewPersonIDFromModel(host, schema string, port uint16, softwareName, id string) (PersonID, error) {
|
||||
result := PersonID{}
|
||||
result.ID = id
|
||||
result.Source = softwareName
|
||||
result.Host = host
|
||||
result.HostSchema = schema
|
||||
result.HostPort = port
|
||||
result.IsPortSupplemented = false
|
||||
|
||||
if softwareName == "forgejo" {
|
||||
result.Path = personIDapiPathV1
|
||||
}
|
||||
result.UnvalidatedInput = result.AsURI()
|
||||
|
||||
// validate Person specific path
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return PersonID{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (id PersonID) AsWebfinger() string {
|
||||
result := fmt.Sprintf("@%s@%s", strings.ToLower(id.ID), strings.ToLower(id.Host))
|
||||
return result
|
||||
}
|
||||
|
||||
func (id PersonID) AsLoginName() string {
|
||||
result := fmt.Sprintf("%s%s", strings.ToLower(id.ID), id.HostSuffix())
|
||||
return result
|
||||
}
|
||||
|
||||
func (id PersonID) HostSuffix() string {
|
||||
var result string
|
||||
if !id.IsPortSupplemented {
|
||||
result = fmt.Sprintf("-%s-%d", strings.ToLower(id.Host), id.HostPort)
|
||||
} else {
|
||||
result = fmt.Sprintf("-%s", strings.ToLower(id.Host))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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", "mastodon", "gotosocial"}, "Source")...)
|
||||
if id.Source == "forgejo" {
|
||||
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
|
||||
if strings.ToLower(id.Path) != personIDapiPathV1 && strings.ToLower(id.Path) != personIDapiPathV1Latest {
|
||||
result = append(result, fmt.Sprintf("path: %q has to be a person specific api path", id.Path))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ----------------------------- ForgePerson -------------------------------------
|
||||
|
||||
// ForgePerson activity data type
|
||||
// swagger:model
|
||||
type ForgePerson struct {
|
||||
// swagger:ignore
|
||||
ap.Actor
|
||||
}
|
||||
|
||||
func (s ForgePerson) MarshalJSON() ([]byte, error) {
|
||||
return s.Actor.MarshalJSON()
|
||||
}
|
||||
|
||||
func (s *ForgePerson) UnmarshalJSON(data []byte) error {
|
||||
return s.Actor.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
func (s ForgePerson) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(string(s.Type), "Type")...)
|
||||
result = append(result, validation.ValidateOneOf(string(s.Type), []any{string(ap.PersonType)}, "Type")...)
|
||||
result = append(result, validation.ValidateNotEmpty(s.PreferredUsername.String(), "PreferredUsername")...)
|
||||
|
||||
return result
|
||||
}
|
268
modules/forgefed/actor_person_test.go
Normal file
268
modules/forgefed/actor_person_test.go
Normal file
|
@ -0,0 +1,268 @@
|
|||
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewPersonIdFromModel(t *testing.T) {
|
||||
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, _ := NewPersonIDFromModel("an.other.host", "https", 443, "forgejo", "1")
|
||||
assert.Equal(t, expected, sut)
|
||||
}
|
||||
|
||||
func TestNewPersonId(t *testing.T) {
|
||||
var sut, expected PersonID
|
||||
var err error
|
||||
|
||||
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 = true
|
||||
expected.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
||||
|
||||
sut, err = NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 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")
|
||||
assert.Equal(t, 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")
|
||||
assert.Equal(t, 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")
|
||||
assert.Equal(t, expected, sut)
|
||||
|
||||
expected = PersonID{}
|
||||
expected.ID = "@me"
|
||||
expected.Source = "gotosocial"
|
||||
expected.HostSchema = "https"
|
||||
expected.Path = ""
|
||||
expected.Host = "an.other.host"
|
||||
expected.HostPort = 443
|
||||
expected.IsPortSupplemented = true
|
||||
expected.UnvalidatedInput = "https://an.other.host/@me"
|
||||
|
||||
sut, err = NewPersonID("https://an.other.host/@me", "gotosocial")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, sut)
|
||||
}
|
||||
|
||||
func TestPersonIdValidation(t *testing.T) {
|
||||
sut := PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejo"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = ""
|
||||
sut.Host = "an.other.host"
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/1"
|
||||
|
||||
result, err := validation.IsValid(sut)
|
||||
assert.False(t, result)
|
||||
require.EqualError(t, err, "Validation Error: forgefed.PersonID: path should not be empty\npath: \"\" has to be a person specific api path")
|
||||
|
||||
sut = PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "mastodon"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = ""
|
||||
sut.Host = "an.other.host"
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/1"
|
||||
|
||||
result, err = validation.IsValid(sut)
|
||||
assert.True(t, result)
|
||||
require.NoError(t, err)
|
||||
|
||||
sut = PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejo"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "path"
|
||||
sut.Host = "an.other.host"
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/path/1"
|
||||
|
||||
result, err = validation.IsValid(sut)
|
||||
assert.False(t, result)
|
||||
require.EqualError(t, err, "Validation Error: forgefed.PersonID: path: \"path\" has to be a person specific api path")
|
||||
|
||||
sut = PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejox"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "api/v1/activitypub/user-id"
|
||||
sut.Host = "an.other.host"
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
||||
|
||||
result, err = validation.IsValid(sut)
|
||||
assert.False(t, result)
|
||||
require.EqualError(t, err, "Validation Error: forgefed.PersonID: Field Source contains the value forgejox, which is not in allowed subset [forgejo gitea mastodon gotosocial]")
|
||||
}
|
||||
|
||||
func TestWebfingerId(t *testing.T) {
|
||||
sut, _ := NewPersonID("https://codeberg.org/api/v1/activitypub/user-id/12345", "forgejo")
|
||||
assert.Equal(t, "@12345@codeberg.org", sut.AsWebfinger())
|
||||
}
|
||||
|
||||
func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
|
||||
var err any
|
||||
_, 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")
|
||||
}
|
||||
_, err = NewPersonID("./api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Errorf("relative uris are not allowed")
|
||||
}
|
||||
_, err = NewPersonID("http://1.2.3.4/api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Errorf("uri may not be ip-4 based")
|
||||
}
|
||||
_, err = NewPersonID("http:///[fe80::1ff:fe23:4567:890a%25eth0]/api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Errorf("uri may not be ip-6 based")
|
||||
}
|
||||
_, err = NewPersonID("https://codeberg.org/api/v1/activitypub/../activitypub/user-id/12345", "forgejo")
|
||||
if err == nil {
|
||||
t.Errorf("uri may not contain relative path elements")
|
||||
}
|
||||
_, err = NewPersonID("https://myuser@an.other.host/api/v1/activitypub/user-id/1", "forgejo")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PersonMarshalJSON(t *testing.T) {
|
||||
sut := ForgePerson{}
|
||||
sut.Type = "Person"
|
||||
sut.PreferredUsername = ap.NaturalLanguageValuesNew()
|
||||
sut.PreferredUsername.Set("en", ap.Content("MaxMuster"))
|
||||
result, _ := sut.MarshalJSON()
|
||||
assert.JSONEq(t, `{"type":"Person","preferredUsername":"MaxMuster"}`, string(result), "Expected string is not equal")
|
||||
}
|
||||
|
||||
func Test_PersonUnmarshalJSON(t *testing.T) {
|
||||
expected := &ForgePerson{
|
||||
Actor: ap.Actor{
|
||||
Type: "Person",
|
||||
PreferredUsername: ap.NaturalLanguageValues{
|
||||
ap.LangRefValue{Ref: "en", Value: []byte("MaxMuster")},
|
||||
},
|
||||
},
|
||||
}
|
||||
sut := new(ForgePerson)
|
||||
err := sut.UnmarshalJSON([]byte(`{"type":"Person","preferredUsername":"MaxMuster"}`))
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalJSON() unexpected error: %v", err)
|
||||
}
|
||||
x, _ := expected.MarshalJSON()
|
||||
y, _ := sut.MarshalJSON()
|
||||
if !reflect.DeepEqual(x, y) {
|
||||
t.Errorf("UnmarshalJSON() expected: %q got: %q", x, y)
|
||||
}
|
||||
|
||||
expectedStr := strings.ReplaceAll(strings.ReplaceAll(`{
|
||||
"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||
"type":"Person",
|
||||
"icon":{"type":"Image","mediaType":"image/png","url":"https://federated-repo.prod.meissa.de/avatar/fa7f9c4af2a64f41b1bef292bf872614"},
|
||||
"url":"https://federated-repo.prod.meissa.de/stargoose9",
|
||||
"inbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10/inbox",
|
||||
"outbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10/outbox",
|
||||
"preferredUsername":"stargoose9",
|
||||
"publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10#main-key",
|
||||
"owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBoj...XAgMBAAE=\n-----END PUBLIC KEY-----\n"}}`,
|
||||
"\n", ""),
|
||||
"\t", "")
|
||||
err = sut.UnmarshalJSON([]byte(expectedStr))
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalJSON() unexpected error: %v", err)
|
||||
}
|
||||
result, _ := sut.MarshalJSON()
|
||||
assert.JSONEq(t, expectedStr, string(result), "Expected string is not equal")
|
||||
}
|
||||
|
||||
func TestForgePersonValidation(t *testing.T) {
|
||||
sut := new(ForgePerson)
|
||||
sut.UnmarshalJSON([]byte(`{"type":"Person","preferredUsername":"MaxMuster"}`))
|
||||
if res, _ := validation.IsValid(sut); !res {
|
||||
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsloginName(t *testing.T) {
|
||||
sut, _ := NewPersonID("https://codeberg.org/api/v1/activitypub/user-id/12345", "forgejo")
|
||||
assert.Equal(t, "12345-codeberg.org", sut.AsLoginName())
|
||||
|
||||
sut, _ = NewPersonID("https://codeberg.org:443/api/v1/activitypub/user-id/12345", "forgejo")
|
||||
assert.Equal(t, "12345-codeberg.org-443", sut.AsLoginName())
|
||||
}
|
52
modules/forgefed/actor_repository.go
Normal file
52
modules/forgefed/actor_repository.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
)
|
||||
|
||||
// ----------------------------- RepositoryID --------------------------------------------
|
||||
|
||||
type RepositoryID struct {
|
||||
ActorID
|
||||
}
|
||||
|
||||
const (
|
||||
repositoryIDapiPathV1 = "api/v1/activitypub/repository-id"
|
||||
repositoryIDapiPathV1Latest = "api/activitypub/repository-id"
|
||||
)
|
||||
|
||||
// Factory function for RepositoryID. Created struct is asserted to be valid.
|
||||
func NewRepositoryID(uri, source string) (RepositoryID, error) {
|
||||
result, err := newActorID(uri)
|
||||
if err != nil {
|
||||
return RepositoryID{}, err
|
||||
}
|
||||
result.Source = source
|
||||
|
||||
// validate Person specific
|
||||
repoID := RepositoryID{result}
|
||||
if valid, err := validation.IsValid(repoID); !valid {
|
||||
return RepositoryID{}, err
|
||||
}
|
||||
|
||||
return repoID, nil
|
||||
}
|
||||
|
||||
func (id RepositoryID) 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")...)
|
||||
if id.Source == "forgejo" {
|
||||
result = append(result, validation.ValidateNotEmpty(id.Path, "path")...)
|
||||
if strings.ToLower(id.Path) != repositoryIDapiPathV1 && strings.ToLower(id.Path) != repositoryIDapiPathV1Latest {
|
||||
result = append(result, fmt.Sprintf("path: %q has to be a repo specific api path", id.Path))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
45
modules/forgefed/actor_repository_test.go
Normal file
45
modules/forgefed/actor_repository_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2023, 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewRepositoryId(t *testing.T) {
|
||||
var sut, expected RepositoryID
|
||||
var err error
|
||||
setting.AppURL = "http://localhost:3000/"
|
||||
|
||||
expected = RepositoryID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.HostSchema = "http"
|
||||
expected.Path = ""
|
||||
expected.Host = "localhost"
|
||||
expected.HostPort = 3000
|
||||
expected.IsPortSupplemented = false
|
||||
expected.UnvalidatedInput = "http://localhost:3000/1"
|
||||
|
||||
_, err = NewRepositoryID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo")
|
||||
require.EqualError(t, err, "Validation Error: forgefed.RepositoryID: path: \"api/v1/activitypub/user-id\" has to be a repo specific api path")
|
||||
|
||||
expected = RepositoryID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.HostSchema = "http"
|
||||
expected.Path = "api/activitypub/repository-id"
|
||||
expected.Host = "localhost"
|
||||
expected.HostPort = 3000
|
||||
expected.IsPortSupplemented = false
|
||||
expected.UnvalidatedInput = "http://localhost:3000/api/activitypub/repository-id/1"
|
||||
sut, err = NewRepositoryID("http://localhost:3000/api/activitypub/repository-id/1", "forgejo")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, sut)
|
||||
}
|
|
@ -4,258 +4,71 @@
|
|||
package forgefed
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewPersonId(t *testing.T) {
|
||||
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 = true
|
||||
expected.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
||||
func TestActorNew(t *testing.T) {
|
||||
sut, err := NewActorID("https://an.other.forgejo.host/api/v1/activitypub/user-id/5")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ActorID{
|
||||
ID: "5",
|
||||
HostSchema: "https",
|
||||
Path: "api/v1/activitypub/user-id",
|
||||
Host: "an.other.forgejo.host",
|
||||
HostPort: 443,
|
||||
UnvalidatedInput: "https://an.other.forgejo.host/api/v1/activitypub/user-id/5",
|
||||
IsPortSupplemented: true,
|
||||
}, sut)
|
||||
|
||||
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)
|
||||
}
|
||||
sut, err = NewActorID("https://an.other.forgejo.host/api/v1/activitypub/actor")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ActorID{
|
||||
ID: "actor",
|
||||
HostSchema: "https",
|
||||
Path: "api/v1/activitypub",
|
||||
Host: "an.other.forgejo.host",
|
||||
HostPort: 443,
|
||||
UnvalidatedInput: "https://an.other.forgejo.host/api/v1/activitypub/actor",
|
||||
IsPortSupplemented: true,
|
||||
}, 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)
|
||||
}
|
||||
|
||||
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) {
|
||||
setting.AppURL = "http://localhost:3000/"
|
||||
expected := RepositoryID{}
|
||||
expected.ID = "1"
|
||||
expected.Source = "forgejo"
|
||||
expected.HostSchema = "http"
|
||||
expected.Path = "api/activitypub/repository-id"
|
||||
expected.Host = "localhost"
|
||||
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 {
|
||||
t.Errorf("expected: %v\n but was: %v\n", expected, sut)
|
||||
}
|
||||
sut, err = NewActorID("https://an.other.gts.host/users/me")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ActorID{
|
||||
ID: "me",
|
||||
HostSchema: "https",
|
||||
Path: "users",
|
||||
Host: "an.other.gts.host",
|
||||
HostPort: 443,
|
||||
UnvalidatedInput: "https://an.other.gts.host/users/me",
|
||||
IsPortSupplemented: true,
|
||||
}, sut)
|
||||
}
|
||||
|
||||
func TestActorIdValidation(t *testing.T) {
|
||||
sut := ActorID{}
|
||||
sut.Source = "forgejo"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "api/v1/activitypub/user-id"
|
||||
sut.Host = "an.other.host"
|
||||
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())
|
||||
}
|
||||
result := sut.Validate()
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, "ID should not be empty", result[0])
|
||||
|
||||
sut = ActorID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejo"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "api/v1/activitypub/user-id"
|
||||
sut.Host = "an.other.host"
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersonIdValidation(t *testing.T) {
|
||||
sut := PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejo"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "path"
|
||||
sut.Host = "an.other.host"
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/path/1"
|
||||
|
||||
_, err := validation.IsValid(sut)
|
||||
if validation.IsErrNotValid(err) && strings.Contains(err.Error(), "path: \"path\" has to be a person specific api path\n") {
|
||||
t.Errorf("validation error expected but was: %v\n", err)
|
||||
}
|
||||
|
||||
sut = PersonID{}
|
||||
sut.ID = "1"
|
||||
sut.Source = "forgejox"
|
||||
sut.HostSchema = "https"
|
||||
sut.Path = "api/v1/activitypub/user-id"
|
||||
sut.Host = "an.other.host"
|
||||
sut.HostPort = 443
|
||||
sut.IsPortSupplemented = true
|
||||
sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/1"
|
||||
if sut.Validate()[0] != "Field Source contains the value forgejox, which is not in allowed subset [forgejo gitea]" {
|
||||
t.Errorf("validation error expected but was: %v\n", sut.Validate()[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebfingerId(t *testing.T) {
|
||||
sut, _ := NewPersonID("https://codeberg.org/api/v1/activitypub/user-id/12345", "forgejo")
|
||||
if sut.AsWebfinger() != "@12345@codeberg.org" {
|
||||
t.Errorf("wrong webfinger: %v", sut.AsWebfinger())
|
||||
}
|
||||
|
||||
sut, _ = NewPersonID("https://Codeberg.org/api/v1/activitypub/user-id/12345", "forgejo")
|
||||
if sut.AsWebfinger() != "@12345@codeberg.org" {
|
||||
t.Errorf("wrong webfinger: %v", sut.AsWebfinger())
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldThrowErrorOnInvalidInput(t *testing.T) {
|
||||
var err any
|
||||
_, err = NewPersonID("", "forgejo")
|
||||
if err == nil {
|
||||
t.Error("empty input should be invalid.")
|
||||
}
|
||||
_, err = NewPersonID("http://localhost:3000/api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Error("localhost uris are not external")
|
||||
}
|
||||
_, err = NewPersonID("./api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Error("relative uris are not allowed")
|
||||
}
|
||||
_, err = NewPersonID("http://1.2.3.4/api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Error("uri may not be ip-4 based")
|
||||
}
|
||||
_, err = NewPersonID("http:///[fe80::1ff:fe23:4567:890a%25eth0]/api/v1/something", "forgejo")
|
||||
if err == nil {
|
||||
t.Error("uri may not be ip-6 based")
|
||||
}
|
||||
_, err = NewPersonID("https://codeberg.org/api/v1/activitypub/../activitypub/user-id/12345", "forgejo")
|
||||
if err == nil {
|
||||
t.Error("uri may not contain relative path elements")
|
||||
}
|
||||
_, err = NewPersonID("https://myuser@an.other.host/api/v1/activitypub/user-id/1", "forgejo")
|
||||
if err == nil {
|
||||
t.Error("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)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PersonMarshalJSON(t *testing.T) {
|
||||
sut := ForgePerson{}
|
||||
sut.Type = "Person"
|
||||
sut.PreferredUsername = ap.NaturalLanguageValuesNew()
|
||||
sut.PreferredUsername.Set("en", ap.Content("MaxMuster"))
|
||||
result, _ := sut.MarshalJSON()
|
||||
if string(result) != "{\"type\":\"Person\",\"preferredUsername\":\"MaxMuster\"}" {
|
||||
t.Errorf("MarshalJSON() was = %q", result)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PersonUnmarshalJSON(t *testing.T) {
|
||||
expected := &ForgePerson{
|
||||
Actor: ap.Actor{
|
||||
Type: "Person",
|
||||
PreferredUsername: ap.NaturalLanguageValues{
|
||||
ap.LangRefValue{Ref: "en", Value: []byte("MaxMuster")},
|
||||
},
|
||||
},
|
||||
}
|
||||
sut := new(ForgePerson)
|
||||
err := sut.UnmarshalJSON([]byte(`{"type":"Person","preferredUsername":"MaxMuster"}`))
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalJSON() unexpected error: %v", err)
|
||||
}
|
||||
x, _ := expected.MarshalJSON()
|
||||
y, _ := sut.MarshalJSON()
|
||||
if !reflect.DeepEqual(x, y) {
|
||||
t.Errorf("UnmarshalJSON() expected: %q got: %q", x, y)
|
||||
}
|
||||
|
||||
expectedStr := strings.ReplaceAll(strings.ReplaceAll(`{
|
||||
"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||
"type":"Person",
|
||||
"icon":{"type":"Image","mediaType":"image/png","url":"https://federated-repo.prod.meissa.de/avatar/fa7f9c4af2a64f41b1bef292bf872614"},
|
||||
"url":"https://federated-repo.prod.meissa.de/stargoose9",
|
||||
"inbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10/inbox",
|
||||
"outbox":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10/outbox",
|
||||
"preferredUsername":"stargoose9",
|
||||
"publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10#main-key",
|
||||
"owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
|
||||
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBoj...XAgMBAAE=\n-----END PUBLIC KEY-----\n"}}`,
|
||||
"\n", ""),
|
||||
"\t", "")
|
||||
err = sut.UnmarshalJSON([]byte(expectedStr))
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalJSON() unexpected error: %v", err)
|
||||
}
|
||||
result, _ := sut.MarshalJSON()
|
||||
if expectedStr != string(result) {
|
||||
t.Errorf("UnmarshalJSON() expected: %q got: %q", expectedStr, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForgePersonValidation(t *testing.T) {
|
||||
sut := new(ForgePerson)
|
||||
sut.UnmarshalJSON([]byte(`{"type":"Person","preferredUsername":"MaxMuster"}`))
|
||||
if res, _ := validation.IsValid(sut); !res {
|
||||
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
|
||||
}
|
||||
result = sut.Validate()
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, "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\"", result[0])
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
68
modules/forgefed/object_user_activity_note.go
Normal file
68
modules/forgefed/object_user_activity_note.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// ForgeFollow activity data type
|
||||
// swagger:model
|
||||
type ForgeUserActivityNote struct {
|
||||
// swagger.ignore
|
||||
ap.Object
|
||||
}
|
||||
|
||||
func NewForgeUserActivityNoteFromAp(item ap.Item) (ForgeUserActivityNote, error) {
|
||||
result := ForgeUserActivityNote{}
|
||||
object := item.(*ap.Object)
|
||||
result.Object = *object
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return ForgeUserActivityNote{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// TODO: Unused - might be removed
|
||||
func newNote(doer *user_model.User, content, id string, published time.Time) (ForgeUserActivityNote, error) {
|
||||
note := ForgeUserActivityNote{}
|
||||
note.Type = ap.NoteType
|
||||
note.AttributedTo = ap.IRI(doer.APActorID())
|
||||
note.Content = ap.NaturalLanguageValues{
|
||||
{
|
||||
Ref: ap.NilLangRef,
|
||||
Value: ap.Content(content),
|
||||
},
|
||||
}
|
||||
note.ID = ap.IRI(id)
|
||||
note.Published = published
|
||||
note.URL = ap.IRI(id)
|
||||
note.To = ap.ItemCollection{
|
||||
ap.IRI("https://www.w3.org/ns/activitystreams#Public"),
|
||||
}
|
||||
note.CC = ap.ItemCollection{
|
||||
ap.IRI(doer.APActorID() + "/followers"),
|
||||
}
|
||||
|
||||
if valid, err := validation.IsValid(note); !valid {
|
||||
return ForgeUserActivityNote{}, err
|
||||
}
|
||||
|
||||
return note, nil
|
||||
}
|
||||
|
||||
func (note ForgeUserActivityNote) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(string(note.Type), "type")...)
|
||||
result = append(result, validation.ValidateOneOf(string(note.Type), []any{"Note"}, "type")...)
|
||||
result = append(result, validation.ValidateNotEmpty(note.Content.String(), "content")...)
|
||||
result = append(result, validation.ValidateIDExists(note.URL, "url")...)
|
||||
|
||||
return result
|
||||
}
|
28
modules/forgefed/object_user_activity_note_test.go
Normal file
28
modules/forgefed/object_user_activity_note_test.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgefed
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func Test_UserActivityNoteValidation(t *testing.T) {
|
||||
sut := ForgeUserActivityNote{}
|
||||
sut.Type = "Note"
|
||||
sut.Content = ap.NaturalLanguageValues{
|
||||
{
|
||||
Ref: ap.NilLangRef,
|
||||
Value: ap.Content("Any Content!"),
|
||||
},
|
||||
}
|
||||
sut.URL = ap.IRI("example.org/user-id/57")
|
||||
|
||||
if res, _ := validation.IsValid(sut); !res {
|
||||
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
|
||||
}
|
||||
}
|
|
@ -35,6 +35,8 @@ type ServeHeaderOptions struct {
|
|||
Filename string
|
||||
CacheDuration time.Duration // defaults to 5 minutes
|
||||
LastModified time.Time
|
||||
AdditionalHeaders http.Header
|
||||
RedirectStatusCode int
|
||||
}
|
||||
|
||||
// ServeSetHeaders sets necessary content serve headers
|
||||
|
@ -82,6 +84,12 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
|
|||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
if opts.AdditionalHeaders != nil {
|
||||
for k, v := range opts.AdditionalHeaders {
|
||||
header[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServeData download file from io.Reader
|
||||
|
|
|
@ -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, false))
|
||||
query.AddQuery(inner_bleve.MatchPhraseQuery(field, "Content", repoIndexerAnalyzer, false, 1.0))
|
||||
}
|
||||
keywordQuery = query
|
||||
} else {
|
||||
keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, false)
|
||||
keywordQuery = inner_bleve.MatchPhraseQuery(opts.Keyword, "Content", repoIndexerAnalyzer, false, 1.0)
|
||||
}
|
||||
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
|
|
|
@ -29,11 +29,12 @@ 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, autoFuzzy bool) *query.MatchPhraseQuery {
|
||||
func MatchPhraseQuery(matchPhrase, field, analyzer string, autoFuzzy bool, boost float64) *query.MatchPhraseQuery {
|
||||
q := bleve.NewMatchPhraseQuery(matchPhrase)
|
||||
q.FieldVal = field
|
||||
q.Analyzer = analyzer
|
||||
q.SetAutoFuzziness(autoFuzzy)
|
||||
q.SetBoost(boost)
|
||||
return q
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
const (
|
||||
issueIndexerAnalyzer = "issueIndexer"
|
||||
issueIndexerDocType = "issueIndexerDocType"
|
||||
issueIndexerLatestVersion = 4
|
||||
issueIndexerLatestVersion = 5
|
||||
)
|
||||
|
||||
const unicodeNormalizeName = "unicodeNormalize"
|
||||
|
@ -69,6 +69,7 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) {
|
|||
|
||||
docMapping.AddFieldMappingsAt("is_public", boolFieldMapping)
|
||||
|
||||
docMapping.AddFieldMappingsAt("index", numberFieldMapping)
|
||||
docMapping.AddFieldMappingsAt("title", textFieldMapping)
|
||||
docMapping.AddFieldMappingsAt("content", textFieldMapping)
|
||||
docMapping.AddFieldMappingsAt("comments", textFieldMapping)
|
||||
|
@ -163,9 +164,15 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
q := bleve.NewBooleanQuery()
|
||||
for _, token := range tokens {
|
||||
innerQ := bleve.NewDisjunctionQuery(
|
||||
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))
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "title", issueIndexerAnalyzer, token.Fuzzy, 2.0),
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "content", issueIndexerAnalyzer, token.Fuzzy, 1.0),
|
||||
inner_bleve.MatchPhraseQuery(token.Term, "comments", issueIndexerAnalyzer, token.Fuzzy, 1.0))
|
||||
|
||||
if issueID, err := token.ParseIssueReference(); err == nil {
|
||||
idQuery := inner_bleve.NumericEqualityQuery(issueID, "index")
|
||||
idQuery.SetBoost(5.0)
|
||||
innerQ.AddQuery(idQuery)
|
||||
}
|
||||
|
||||
switch token.Kind {
|
||||
case internal.BoolOptMust:
|
||||
|
|
|
@ -5,6 +5,7 @@ package db
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
issue_model "forgejo.org/models/issues"
|
||||
|
@ -71,6 +72,17 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
)),
|
||||
),
|
||||
)
|
||||
|
||||
term := options.Keyword
|
||||
if term[0] == '#' || term[0] == '!' {
|
||||
term = term[1:]
|
||||
}
|
||||
if issueID, err := strconv.ParseInt(term, 10, 64); err == nil {
|
||||
cond = builder.Or(
|
||||
builder.Eq{"`index`": issueID},
|
||||
cond,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
opt, err := ToDBOptions(ctx, options)
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
issueIndexerLatestVersion = 1
|
||||
issueIndexerLatestVersion = 2
|
||||
// multi-match-types, currently only 2 types are used
|
||||
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
|
||||
esMultiMatchTypeBestFields = "best_fields"
|
||||
|
@ -56,7 +56,8 @@ const (
|
|||
"repo_id": { "type": "long", "index": true },
|
||||
"is_public": { "type": "boolean", "index": true },
|
||||
|
||||
"title": { "type": "text", "index": true },
|
||||
"index": { "type": "long", "index": true },
|
||||
"title": { "type": "text", "index": true },
|
||||
"content": { "type": "text", "index": true },
|
||||
"comments": { "type" : "text", "index": true },
|
||||
|
||||
|
@ -155,21 +156,25 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
return nil, err
|
||||
}
|
||||
for _, token := range tokens {
|
||||
innerQ := elastic.NewMultiMatchQuery(token.Term, "title", "content", "comments")
|
||||
innerQ := elastic.NewMultiMatchQuery(token.Term, "content", "comments").FieldWithBoost("title", 2.0).TieBreaker(0.5)
|
||||
if token.Fuzzy {
|
||||
// If the term is not a phrase use fuzziness set to AUTO
|
||||
innerQ = innerQ.Type(esMultiMatchTypeBestFields).Fuzziness(esFuzzyAuto)
|
||||
} else {
|
||||
innerQ = innerQ.Type(esMultiMatchTypePhrasePrefix)
|
||||
}
|
||||
|
||||
var eitherQ elastic.Query = innerQ
|
||||
if issueID, err := token.ParseIssueReference(); err == nil {
|
||||
indexQ := elastic.NewTermQuery("index", issueID).Boost(15.0)
|
||||
eitherQ = elastic.NewDisMaxQuery().Query(indexQ).Query(innerQ).TieBreaker(0.5)
|
||||
}
|
||||
switch token.Kind {
|
||||
case internal.BoolOptMust:
|
||||
q.Must(innerQ)
|
||||
q.Must(eitherQ)
|
||||
case internal.BoolOptShould:
|
||||
q.Should(innerQ)
|
||||
q.Should(eitherQ)
|
||||
case internal.BoolOptNot:
|
||||
q.MustNot(innerQ)
|
||||
q.MustNot(eitherQ)
|
||||
}
|
||||
}
|
||||
query.Must(q)
|
||||
|
|
|
@ -14,6 +14,7 @@ type IndexerData struct {
|
|||
ID int64 `json:"id"`
|
||||
RepoID int64 `json:"repo_id"`
|
||||
IsPublic bool `json:"is_public"` // If the repo is public
|
||||
Index int64 `json:"index"`
|
||||
|
||||
// Fields used for keyword searching
|
||||
Title string `json:"title"`
|
||||
|
|
|
@ -5,6 +5,7 @@ package internal
|
|||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -22,6 +23,14 @@ type Token struct {
|
|||
Fuzzy bool
|
||||
}
|
||||
|
||||
func (tk *Token) ParseIssueReference() (int64, error) {
|
||||
term := tk.Term
|
||||
if term[0] == '#' || term[0] == '!' {
|
||||
term = term[1:]
|
||||
}
|
||||
return strconv.ParseInt(term, 10, 64)
|
||||
}
|
||||
|
||||
type Tokenizer struct {
|
||||
in *strings.Reader
|
||||
}
|
||||
|
|
|
@ -549,6 +549,55 @@ var cases = []*testIndexerCase{
|
|||
}), result.Total)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Index",
|
||||
SearchOptions: &internal.SearchOptions{
|
||||
Keyword: "13",
|
||||
SortBy: internal.SortByScore,
|
||||
RepoIDs: []int64{5},
|
||||
},
|
||||
ExpectedIDs: []int64{93}, // 93 = #13 in repo 5
|
||||
ExpectedTotal: 1,
|
||||
},
|
||||
{
|
||||
Name: "Index with prefix",
|
||||
SearchOptions: &internal.SearchOptions{
|
||||
Keyword: "#13",
|
||||
SortBy: internal.SortByScore,
|
||||
RepoIDs: []int64{5},
|
||||
},
|
||||
ExpectedIDs: []int64{93},
|
||||
ExpectedTotal: 1,
|
||||
},
|
||||
{
|
||||
Name: "Index and title boost",
|
||||
ExtraData: []*internal.IndexerData{
|
||||
{ID: 1001, Title: "re #13", RepoID: 5},
|
||||
{ID: 1002, Title: "re #1001", Content: "leave 13 alone. - 13", RepoID: 5},
|
||||
},
|
||||
SearchOptions: &internal.SearchOptions{
|
||||
Keyword: "!13",
|
||||
SortBy: internal.SortByScore,
|
||||
RepoIDs: []int64{5},
|
||||
},
|
||||
ExpectedIDs: []int64{93, 1001, 1002},
|
||||
ExpectedTotal: 3,
|
||||
},
|
||||
{
|
||||
Name: "Index exclude",
|
||||
ExtraData: []*internal.IndexerData{
|
||||
{ID: 1001, Index: 101, Title: "Brrr", RepoID: 5},
|
||||
{ID: 1002, Index: 102, Title: "Brrr", Content: "Brrr", RepoID: 5},
|
||||
{ID: 1003, Index: 103, Title: "Brrr", RepoID: 5},
|
||||
{ID: 1004, Index: 104, Title: "Brrr", RepoID: 5},
|
||||
},
|
||||
SearchOptions: &internal.SearchOptions{
|
||||
Keyword: "Brrr -101 -103",
|
||||
SortBy: internal.SortByScore,
|
||||
},
|
||||
ExpectedIDs: []int64{1002, 1004},
|
||||
ExpectedTotal: 2,
|
||||
},
|
||||
{
|
||||
Name: "SortByCreatedDesc",
|
||||
SearchOptions: &internal.SearchOptions{
|
||||
|
@ -741,6 +790,7 @@ func generateDefaultIndexerData() []*internal.IndexerData {
|
|||
|
||||
data = append(data, &internal.IndexerData{
|
||||
ID: id,
|
||||
Index: issueIndex,
|
||||
RepoID: repoID,
|
||||
IsPublic: repoID%2 == 0,
|
||||
Title: fmt.Sprintf("issue%d of repo%d", issueIndex, repoID),
|
||||
|
|
|
@ -95,6 +95,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
|
|||
return &internal.IndexerData{
|
||||
ID: issue.ID,
|
||||
RepoID: issue.RepoID,
|
||||
Index: issue.Index,
|
||||
IsPublic: !issue.Repo.IsPrivate,
|
||||
Title: issue.Title,
|
||||
Content: issue.Content,
|
||||
|
|
|
@ -84,6 +84,13 @@ func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) {
|
|||
func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
|
||||
var image oci.Image
|
||||
if err := json.NewDecoder(r).Decode(&image); err != nil {
|
||||
// Handle empty config blobs (common in OCI artifacts)
|
||||
if err == io.EOF {
|
||||
return &Metadata{
|
||||
Type: TypeOCI,
|
||||
Platform: DefaultPlatform,
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -60,3 +61,49 @@ func TestParseImageConfig(t *testing.T) {
|
|||
assert.Equal(t, projectURL, metadata.ProjectURL)
|
||||
assert.Equal(t, repositoryURL, metadata.RepositoryURL)
|
||||
}
|
||||
|
||||
func TestParseImageConfigEmptyBlob(t *testing.T) {
|
||||
t.Run("Empty config blob (EOF)", func(t *testing.T) {
|
||||
// Test empty reader (simulates empty config blob common in OCI artifacts)
|
||||
metadata, err := ParseImageConfig(oci.MediaTypeImageManifest, strings.NewReader(""))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, TypeOCI, metadata.Type)
|
||||
assert.Equal(t, DefaultPlatform, metadata.Platform)
|
||||
assert.Empty(t, metadata.Description)
|
||||
assert.Empty(t, metadata.Authors)
|
||||
assert.Empty(t, metadata.Labels)
|
||||
assert.Empty(t, metadata.Manifests)
|
||||
})
|
||||
|
||||
t.Run("Empty JSON object", func(t *testing.T) {
|
||||
// Test minimal valid JSON config
|
||||
metadata, err := ParseImageConfig(oci.MediaTypeImageManifest, strings.NewReader("{}"))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, TypeOCI, metadata.Type)
|
||||
assert.Equal(t, DefaultPlatform, metadata.Platform)
|
||||
assert.Empty(t, metadata.Description)
|
||||
assert.Empty(t, metadata.Authors)
|
||||
})
|
||||
|
||||
t.Run("Invalid JSON still returns error", func(t *testing.T) {
|
||||
// Test that actual JSON errors (not EOF) are still returned
|
||||
_, err := ParseImageConfig(oci.MediaTypeImageManifest, strings.NewReader("{invalid json"))
|
||||
require.Error(t, err)
|
||||
assert.NotEqual(t, io.EOF, err)
|
||||
})
|
||||
|
||||
t.Run("OCI artifact with empty config", func(t *testing.T) {
|
||||
// Test OCI artifact scenario with minimal config
|
||||
configOCI := `{"config": {}}`
|
||||
metadata, err := ParseImageConfig(oci.MediaTypeImageManifest, strings.NewReader(configOCI))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, TypeOCI, metadata.Type)
|
||||
assert.Equal(t, DefaultPlatform, metadata.Platform)
|
||||
assert.Empty(t, metadata.Description)
|
||||
assert.Empty(t, metadata.Authors)
|
||||
assert.Empty(t, metadata.ImageLayers)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -69,8 +69,11 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re
|
|||
|
||||
// insert units for repo
|
||||
defaultUnits := unit.DefaultRepoUnits
|
||||
if isFork {
|
||||
switch {
|
||||
case isFork:
|
||||
defaultUnits = unit.DefaultForkRepoUnits
|
||||
case repo.IsMirror:
|
||||
defaultUnits = unit.DefaultMirrorRepoUnits
|
||||
}
|
||||
units := make([]repo_model.RepoUnit, 0, len(defaultUnits))
|
||||
for _, tp := range defaultUnits {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
@ -52,6 +53,7 @@ var (
|
|||
DisabledRepoUnits []string
|
||||
DefaultRepoUnits []string
|
||||
DefaultForkRepoUnits []string
|
||||
DefaultMirrorRepoUnits []string
|
||||
PrefixArchiveFiles bool
|
||||
DisableMigrations bool
|
||||
DisableStars bool
|
||||
|
@ -175,6 +177,7 @@ var (
|
|||
DisabledRepoUnits: []string{},
|
||||
DefaultRepoUnits: []string{},
|
||||
DefaultForkRepoUnits: []string{},
|
||||
DefaultMirrorRepoUnits: []string{},
|
||||
PrefixArchiveFiles: true,
|
||||
DisableMigrations: false,
|
||||
DisableStars: false,
|
||||
|
|
|
@ -56,7 +56,7 @@ var SSH = struct {
|
|||
Domain: "",
|
||||
Port: 22,
|
||||
ServerCiphers: []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"},
|
||||
ServerKeyExchanges: []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"},
|
||||
ServerKeyExchanges: []string{"mlkem768x25519-sha256", "curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"},
|
||||
ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"},
|
||||
KeygenPath: "",
|
||||
MinimumKeySizeCheck: true,
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
package structs
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ActionRunJob represents a job of a run
|
||||
// swagger:model
|
||||
type ActionRunJob struct {
|
||||
|
@ -23,3 +27,54 @@ type ActionRunJob struct {
|
|||
// the action run job status
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ActionRun represents an action run
|
||||
// swagger:model
|
||||
type ActionRun struct {
|
||||
// the action run id
|
||||
ID int64 `json:"id"`
|
||||
// the action run's title
|
||||
Title string `json:"title"`
|
||||
// the repo this action is part of
|
||||
Repo *Repository `json:"repository"`
|
||||
// the name of workflow file
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
// a unique number for each run of a repository
|
||||
Index int64 `json:"index_in_repo"`
|
||||
// the user that triggered this action run
|
||||
TriggerUser *User `json:"trigger_user"`
|
||||
// the cron id for the schedule trigger
|
||||
ScheduleID int64
|
||||
// the commit/tag/… the action run ran on
|
||||
PrettyRef string `json:"prettyref"`
|
||||
// has the commit/tag/… the action run ran on been deleted
|
||||
IsRefDeleted bool `json:"is_ref_deleted"`
|
||||
// the commit sha the action run ran on
|
||||
CommitSHA string `json:"commit_sha"`
|
||||
// If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
|
||||
IsForkPullRequest bool `json:"is_fork_pull_request"`
|
||||
// may need approval if it's a fork pull request
|
||||
NeedApproval bool `json:"need_approval"`
|
||||
// who approved this action run
|
||||
ApprovedBy int64 `json:"approved_by"`
|
||||
// the webhook event that causes the workflow to run
|
||||
Event string `json:"event"`
|
||||
// the payload of the webhook event that causes the workflow to run
|
||||
EventPayload string `json:"event_payload"`
|
||||
// the trigger event defined in the `on` configuration of the triggered workflow
|
||||
TriggerEvent string `json:"trigger_event"`
|
||||
// the current status of this run
|
||||
Status string `json:"status"`
|
||||
// when the action run was started
|
||||
Started time.Time `json:"started,omitempty"`
|
||||
// when the action run was stopped
|
||||
Stopped time.Time `json:"stopped,omitempty"`
|
||||
// when the action run was created
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
// when the action run was last updated
|
||||
Updated time.Time `json:"updated,omitempty"`
|
||||
// how long the action run ran for
|
||||
Duration time.Duration `json:"duration,omitempty"`
|
||||
// the url of this action run
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ var (
|
|||
_ Payloader = &RepositoryPayload{}
|
||||
_ Payloader = &ReleasePayload{}
|
||||
_ Payloader = &PackagePayload{}
|
||||
_ Payloader = &ActionPayload{}
|
||||
)
|
||||
|
||||
// _________ __
|
||||
|
@ -484,3 +485,36 @@ type PackagePayload struct {
|
|||
func (p *PackagePayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
||||
// _ _ _
|
||||
// / \ ___| |_(_) ___ _ __
|
||||
// / _ \ / __| __| |/ _ \| '_ \
|
||||
// / ___ \ (__| |_| | (_) | | | |
|
||||
// /_/ \_\___|\__|_|\___/|_| |_|
|
||||
|
||||
// this name is ridiculous, yes
|
||||
// it's the sub-type of hook that has something to do with Forgejo Actions
|
||||
type HookActionAction string
|
||||
|
||||
const (
|
||||
HookActionFailure HookActionAction = "failure"
|
||||
HookActionRecover HookActionAction = "recover"
|
||||
HookActionSuccess HookActionAction = "success"
|
||||
)
|
||||
|
||||
// ActionPayload payload for action webhooks
|
||||
type ActionPayload struct {
|
||||
Action HookActionAction `json:"action"`
|
||||
Run *ActionRun `json:"run"`
|
||||
// the status of this run before it completed
|
||||
// this must be a not done status
|
||||
PriorStatus string `json:"prior_status"`
|
||||
// the last run for the same workflow
|
||||
// could be nil when Run is the first for it's workflow
|
||||
LastRun *ActionRun `json:"last_run,omitempty"`
|
||||
}
|
||||
|
||||
// JSONPayload return payload information
|
||||
func (p *ActionPayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
|
|
@ -32,3 +32,23 @@ type ActionTaskResponse struct {
|
|||
Entries []*ActionTask `json:"workflow_runs"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
}
|
||||
|
||||
// ActionRun represents an ActionRun
|
||||
type RepoActionRun struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
RunNumber int64 `json:"run_number"`
|
||||
Event string `json:"event"`
|
||||
Status string `json:"status"`
|
||||
HeadBranch string `json:"head_branch"`
|
||||
HeadSHA string `json:"head_sha"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
URL string `json:"url"`
|
||||
TriggeringActor *User `json:"triggering_actor"`
|
||||
}
|
||||
|
||||
// ListActionRunResponse return a list of ActionRun
|
||||
type ListRepoActionRunResponse struct {
|
||||
Entries []*RepoActionRun `json:"workflow_runs"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
}
|
||||
|
|
|
@ -75,6 +75,11 @@ func IsValidExternalURL(uri string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// IsValidReleaseAssetURL checks if the URL is valid for external release assets
|
||||
func IsValidReleaseAssetURL(uri string) bool {
|
||||
return IsValidURL(uri)
|
||||
}
|
||||
|
||||
// IsValidExternalTrackerURLFormat checks if URL matches required syntax for external trackers
|
||||
func IsValidExternalTrackerURLFormat(uri string) bool {
|
||||
if !IsValidExternalURL(uri) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package webhook
|
||||
|
||||
// HookEvents is a set of web hook events
|
||||
// update TestCreateWebhook in models/webhook/webhook_test.go when adding or changing values here
|
||||
type HookEvents struct {
|
||||
Create bool `json:"create"`
|
||||
Delete bool `json:"delete"`
|
||||
|
@ -26,9 +27,12 @@ type HookEvents struct {
|
|||
Repository bool `json:"repository"`
|
||||
Release bool `json:"release"`
|
||||
Package bool `json:"package"`
|
||||
ActionRunFailure bool `json:"action_run_failure"`
|
||||
ActionRunRecover bool `json:"action_run_recover"`
|
||||
ActionRunSuccess bool `json:"action_run_success"`
|
||||
}
|
||||
|
||||
// HookEvent represents events that will delivery hook.
|
||||
// HookEvent represents events that will deliver a hook.
|
||||
type HookEvent struct {
|
||||
PushOnly bool `json:"push_only"`
|
||||
SendEverything bool `json:"send_everything"`
|
||||
|
|
|
@ -7,6 +7,7 @@ package webhook
|
|||
type HookEventType string
|
||||
|
||||
// Types of hook events
|
||||
// update TestCreateWebhook in models/webhook/webhook_test.go when adding or changing values here
|
||||
const (
|
||||
HookEventCreate HookEventType = "create"
|
||||
HookEventDelete HookEventType = "delete"
|
||||
|
@ -33,6 +34,9 @@ const (
|
|||
HookEventPackage HookEventType = "package"
|
||||
HookEventSchedule HookEventType = "schedule"
|
||||
HookEventWorkflowDispatch HookEventType = "workflow_dispatch"
|
||||
HookEventActionRunFailure HookEventType = "action_run_failure"
|
||||
HookEventActionRunRecover HookEventType = "action_run_recover"
|
||||
HookEventActionRunSuccess HookEventType = "action_run_success"
|
||||
)
|
||||
|
||||
// Event returns the HookEventType as an event string
|
||||
|
@ -65,6 +69,12 @@ func (h HookEventType) Event() string {
|
|||
return "repository"
|
||||
case HookEventRelease:
|
||||
return "release"
|
||||
case HookEventActionRunFailure:
|
||||
return "action_run_failure"
|
||||
case HookEventActionRunRecover:
|
||||
return "action_run_recover"
|
||||
case HookEventActionRunSuccess:
|
||||
return "action_run_success"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -2915,6 +2915,13 @@ comment.blocked_by_user = Komentování není možné, protože jste byli zablok
|
|||
sync_fork.branch_behind_few = Tato větev je %[1]d revizí pozadu za %[2]s
|
||||
sync_fork.button = Synchronizovat
|
||||
sync_fork.branch_behind_one = Tato větev je %[1]d revizi pozadu za %[2]s
|
||||
settings.event_action_failure = Selhání
|
||||
settings.event_action_failure_desc = Běh akce selhal.
|
||||
settings.event_action_recover = Obnovit
|
||||
settings.event_action_success = Úspěch
|
||||
settings.event_action_success_desc = Běh akce byl úspěšný.
|
||||
settings.event_header_action = Události běhu akce
|
||||
settings.event_action_recover_desc = Běh akce byl úspěšný, předchozí běh akce ve stejném workflow selhal.
|
||||
|
||||
[graphs]
|
||||
component_loading_info = Tohle může chvíli trvat…
|
||||
|
|
|
@ -10,7 +10,7 @@ logo = Logo
|
|||
sign_in = Login
|
||||
sign_in_with_provider = Login med %s
|
||||
sign_in_or = eller
|
||||
sign_out = Logud
|
||||
sign_out = Log ud
|
||||
sign_up = Register
|
||||
return_to_forgejo = Vend tilbage til Forgejo
|
||||
new_repo.title = Ny repository
|
||||
|
@ -1432,7 +1432,7 @@ issues.new.no_items = Ingen elementer
|
|||
issues.new.milestone = Milepæl
|
||||
issues.new.no_milestone = Ingen milepæl
|
||||
issues.filter_assignees = Filter tildelt
|
||||
issues.filter_milestones = Filter Milepæl
|
||||
issues.filter_milestones = Filter milepæl
|
||||
issues.filter_projects = Filter projekt
|
||||
issues.filter_labels = Filter etiket
|
||||
issues.filter_reviewers = Filter anmelder
|
||||
|
@ -2018,7 +2018,7 @@ settings.lfs_pointers.inRepo = i depot
|
|||
settings.lfs_pointers.exists = Eksisterer i lager
|
||||
settings.lfs_pointers.accessible = Tilgængeligt for bruger
|
||||
signing.wont_sign.not_signed_in = Du er ikke logget ind.
|
||||
wiki.welcome = Velkommen til Wikien
|
||||
wiki.welcome = Velkommen til wikien
|
||||
milestones.modify = Opdater milepæl
|
||||
milestones.edit_success = Milepæl "%s" er blevet opdateret.
|
||||
milestones.filter_sort.least_issues = Mindst problemer
|
||||
|
@ -2262,7 +2262,7 @@ settings.wiki_delete_notices_1 = - Dette vil permanent slette og deaktivere depo
|
|||
settings.wiki_branch_rename_failure = Det lykkedes ikke at normalisere depotwikiens filialnavn.
|
||||
settings.add_collaborator_duplicate = Samarbejdspartneren er allerede føjet til dette depot.
|
||||
settings.add_collaborator_owner = Kan ikke tilføje en ejer som samarbejdspartner.
|
||||
settings.collaborator_deletion = Fjern Samarbejdspartner
|
||||
settings.collaborator_deletion = Fjern samarbejdspartner
|
||||
settings.collaborator_deletion_desc = Fjernelse af en samarbejdspartner vil tilbagekalde deres adgang til dette depot. Vil du fortsætte?
|
||||
settings.add_team_duplicate = Teamet har allerede depotet
|
||||
settings.add_collaborator_blocked_our = Samarbejdspartneren kan ikke tilføjes, fordi depots ejer har blokeret dem.
|
||||
|
@ -2728,6 +2728,13 @@ comment.blocked_by_user = Det er ikke muligt at kommentere, fordi du er blokeret
|
|||
sync_fork.branch_behind_few = Denne gren er %[1]d commits bag %[2]s
|
||||
sync_fork.button = Sync
|
||||
sync_fork.branch_behind_one = Denne gren er %[1]d commit bag %[2]s
|
||||
settings.event_header_action = Handling Run-begivenheder
|
||||
settings.event_action_failure = Mislykket
|
||||
settings.event_action_success_desc = Handlingen blev udført.
|
||||
settings.event_action_success = Success
|
||||
settings.event_action_recover_desc = Handlingskørsel lykkedes efter at den sidste handlingskørsel i samme arbejdsgang mislykkedes.
|
||||
settings.event_action_failure_desc = Handlingskørsel sluttede som en fejl.
|
||||
settings.event_action_recover = Gendan
|
||||
|
||||
[notification]
|
||||
watching = Overvåger
|
||||
|
@ -3529,8 +3536,8 @@ composer.install = For at installere pakken ved hjælp af Composer skal du køre
|
|||
container.multi_arch = OS / Arch
|
||||
rubygems.required.ruby = Kræver Ruby version
|
||||
swift.install = Tilføj pakken i din <code>Package.swift</code>-fil:
|
||||
settings.link.select = Vælg Depot
|
||||
settings.link.button = Opdater Depot Link
|
||||
settings.link.select = Vælg depot
|
||||
settings.link.button = Opdater depot link
|
||||
settings.link.error = Kunne ikke opdatere depotlinket.
|
||||
owner.settings.cargo.initialize.success = Cargo-indekset blev oprettet.
|
||||
owner.settings.cargo.rebuild.description = Genopbygning kan være nyttig, hvis indekset ikke er synkroniseret med de lagrede Cargo-pakker.
|
||||
|
|
|
@ -745,7 +745,7 @@ social=Soziale Konten
|
|||
applications=Anwendungen
|
||||
orgs=Organisationen
|
||||
repos=Repositorys
|
||||
delete=Konto löschen
|
||||
delete=Account löschen
|
||||
twofa=Zwei-Faktor-Authentifizierung (TOTP)
|
||||
account_link=Verknüpfte Benutzerkonten
|
||||
organization=Organisationen
|
||||
|
@ -1511,7 +1511,7 @@ projects.card_type.images_and_text=Bilder und Text
|
|||
projects.card_type.text_only=Nur Text
|
||||
|
||||
issues.desc=Verwalte Bug-Reports, Aufgaben und Meilensteine.
|
||||
issues.filter_assignees=Filter
|
||||
issues.filter_assignees=Verantwortliche filtern
|
||||
issues.filter_milestones=Meilenstein filtern
|
||||
issues.filter_projects=Projekt filtern
|
||||
issues.filter_labels=Label filtern
|
||||
|
@ -4031,7 +4031,7 @@ repo_kind = Repos suchen …
|
|||
user_kind = Benutzer suchen …
|
||||
org_kind = Orgs suchen …
|
||||
team_kind = Teams suchen …
|
||||
code_kind = Code suchen…
|
||||
code_kind = Code durchsuchen …
|
||||
package_kind = Pakete suchen …
|
||||
project_kind = Projekte suchen …
|
||||
branch_kind = Branches suchen …
|
||||
|
|
|
@ -2486,6 +2486,13 @@ settings.event_pull_request_review_request_desc = Pull request review requested
|
|||
settings.event_pull_request_approvals = Pull request approvals
|
||||
settings.event_pull_request_merge = Pull request merge
|
||||
settings.event_pull_request_enforcement = Enforcement
|
||||
settings.event_header_action = Action Run events
|
||||
settings.event_action_failure = Failure
|
||||
settings.event_action_failure_desc = Action Run ended as failure.
|
||||
settings.event_action_recover = Recover
|
||||
settings.event_action_recover_desc = Action Run succeeded after last Action Run in the same workflow failed.
|
||||
settings.event_action_success = Success
|
||||
settings.event_action_success_desc = Action Run succeeded.
|
||||
settings.event_package = Package
|
||||
settings.event_package_desc = Package created or deleted in a repository.
|
||||
settings.branch_filter = Branch filter
|
||||
|
@ -2978,8 +2985,6 @@ teams.invite_team_member.list = Pending invitations
|
|||
teams.delete_team_title = Delete team
|
||||
teams.delete_team_desc = Deleting a team revokes repository access from its members. Continue?
|
||||
teams.delete_team_success = The team has been deleted.
|
||||
teams.read_permission_desc = This team grants <strong>Read</strong> access: members can view and clone team repositories.
|
||||
teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to team repositories.
|
||||
teams.admin_permission_desc = This team grants <strong>Administrator</strong> access: members can read from, push to and add collaborators to team repositories.
|
||||
teams.create_repo_permission_desc = Additionally, this team grants <strong>Create repository</strong> permission: members can create new repositories in organization.
|
||||
teams.repositories = Team repositories
|
||||
|
|
|
@ -103,7 +103,7 @@ ok = Bone
|
|||
download_logs = Elsuti protokolojn
|
||||
unknown = Nekonata
|
||||
issues = Eraroj
|
||||
error404 = Aŭ tiu ĉi paĝo <strong>ne ekzistas</strong> aŭ <strong>vi ne rajtas</strong> vidi ĝin.
|
||||
error404 = Aŭ tiu ĉi paĝo <strong>ne ekzistas</strong>, <strong>estis forigita</strong> aŭ <strong>vi ne rajtas</strong> vidi ĝin.
|
||||
retry = Reprovi
|
||||
activities = Aktivecoj
|
||||
confirm_delete_selected = Konfirmi forigon de ĉiu elektito?
|
||||
|
@ -171,6 +171,7 @@ table_modal.placeholder.header = Kapo
|
|||
table_modal.placeholder.content = Enhavo
|
||||
table_modal.label.rows = Horizontaloj
|
||||
table_modal.label.columns = Vertikaloj
|
||||
link_modal.description = Priskribo
|
||||
|
||||
[aria]
|
||||
navbar = Esplora breto
|
||||
|
@ -523,6 +524,7 @@ totp_enrolled.text_1.has_webauthn = Vi ĵus aktivigis TOTP-n por via konto. Tio
|
|||
totp_enrolled.text_1.no_webauthn = Vi ĵus aktivigis TOTP-n por via konto. Tio volas diri ke por ĉiuj venontaj salutoj al via konto, vi devos uzi TOTP-n kiel 2FA metodo.
|
||||
removed_security_key.no_2fa = Ne estas aliaj 2FA agorditaj metodoj, tio estas ke ne plus necesas uzi 2FA-n por saluti.
|
||||
totp_disabled.no_2fa = Ne estas plu aliaj 2FA agorditaj metodoj, tio estas ke ne plus necesas uzi 2FA-n por saluti.
|
||||
account_security_caution.text_1 = Se tio estis vi, vi povas sekure ignori ĉi tiun retmesaĝon.
|
||||
|
||||
[form]
|
||||
TeamName = Gruponomo
|
||||
|
@ -861,30 +863,30 @@ npm.details.tag = Etikedo
|
|||
|
||||
|
||||
[search]
|
||||
search = Serĉi...
|
||||
search = Serĉi…
|
||||
regexp = RegEsp
|
||||
milestone_kind = Serĉi celojn...
|
||||
code_search_by_git_grep = Nunaj rezultoj de kodoserĉo estas provizitaj de "git grep". Eble estas plibonaj rezultoj se la retejestro aktivigas la indeksilon de kodo.
|
||||
code_search_unavailable = Kodoserĉo ne haveblas nune. Bonvolu kontakti la retejestron.
|
||||
package_kind = Serĉi pakojn...
|
||||
package_kind = Serĉi pakojn…
|
||||
type_tooltip = Serĉotipo
|
||||
user_kind = Serĉi uzantojn...
|
||||
user_kind = Serĉi uzantojn…
|
||||
fuzzy_tooltip = Inkluzivas rezultojn proksime kongruantajn kun la serĉoterminoj
|
||||
repo_kind = Serĉi deponejojn...
|
||||
org_kind = Serĉi organizaĵojn...
|
||||
code_kind = Serĉi kodon...
|
||||
project_kind = Serĉi projektojn...
|
||||
team_kind = Serĉi teamojn...
|
||||
repo_kind = Serĉi deponejojn…
|
||||
org_kind = Serĉi organizaĵojn…
|
||||
code_kind = Serĉi kodon…
|
||||
project_kind = Serĉi projektojn…
|
||||
team_kind = Serĉi teamojn…
|
||||
keyword_search_unavailable = Serĉo per ŝlosilvortoj ne haveblas nune. Bonvolu kontakti la retejestron.
|
||||
union = Ŝlosilvortoj
|
||||
union_tooltip = Inkluzivas rezultojn kongruantajn kun la ajnaj blankaspacitaj ŝlosilvortoj
|
||||
commit_kind = Serĉi enmetojn...
|
||||
commit_kind = Serĉi enmetojn…
|
||||
no_results = Ne trovis kongruantajn rezultojn.
|
||||
exact = Ĝusta
|
||||
exact_tooltip = Inkluzivas nur rezultojn kongruantajn kun la ĝustaj serĉoterminoj
|
||||
issue_kind = Serĉi erarojn...
|
||||
regexp_tooltip = Interpretas la serĉoterminoj kiel regulesprimo
|
||||
fuzzy = Svaga
|
||||
branch_kind = Serĉi disbranĉigojn...
|
||||
branch_kind = Serĉi disbranĉigojn…
|
||||
runner_kind = Serĉi rulantojn...
|
||||
pull_kind = Serĉi tirpetojn...
|
|
@ -8,7 +8,7 @@ sign_in=Kirjaudu sisään
|
|||
sign_in_or=tai
|
||||
sign_out=Kirjaudu ulos
|
||||
sign_up=Rekisteröidy
|
||||
link_account=Yhdistä tili
|
||||
link_account=Linkitä tili
|
||||
register=Rekisteröidy
|
||||
version=Versio
|
||||
powered_by=Voimanlähteenä %s
|
||||
|
@ -23,7 +23,7 @@ toc=Sisällysluettelo
|
|||
licenses=Lisenssit
|
||||
return_to_forgejo=Palaa Forgejohon
|
||||
|
||||
username=Käyttäjätunnus
|
||||
username=Käyttäjänimi
|
||||
email=Sähköpostiosoite
|
||||
password=Salasana
|
||||
access_token=Pääsypoletti
|
||||
|
@ -31,7 +31,7 @@ re_type=Vahvista salasana
|
|||
captcha=CAPTCHA
|
||||
twofa=Kaksivaiheinen todennus
|
||||
twofa_scratch=Kaksivaiheinen kertakäyttöinen koodi
|
||||
passcode=Tunnuskoodi
|
||||
passcode=Pääsykoodi
|
||||
|
||||
webauthn_insert_key=Aseta turva-avaimesi
|
||||
webauthn_sign_in=Paina turva-avaimesi painiketta. Jos turva-avaimessasi ei ole painiketta, irrota se ja aseta uudelleen.
|
||||
|
@ -40,7 +40,7 @@ webauthn_use_twofa=Käytä kaksivaihesta todennusta puhelimestasi
|
|||
webauthn_error=Turva-avainta ei voitu lukea.
|
||||
webauthn_unsupported_browser=Selaimesi ei tällä hetkellä tue WebAuthnia.
|
||||
webauthn_error_unknown=Tuntematon virhe. Yritä uudelleen.
|
||||
webauthn_error_insecure=`WebAuthn tukee vain suojattuja yhteyksiä. Testaukseen HTTP:n yli, voit käyttää osoitetta "localhost" tai "127.0.0.1"`
|
||||
webauthn_error_insecure=WebAuthn tukee vain suojattuja yhteyksiä. Testatessa HTTP-yhteydellä voit käyttää osoitetta "localhost" tai "127.0.0.1"
|
||||
webauthn_error_unable_to_process=Palvelin ei pystynyt käsittelemään pyyntöä.
|
||||
webauthn_error_duplicated=Turva-avainta ei ole sallittu tässä pyynnössä. Varmista, ettei avainta ole jo rekisteröity.
|
||||
webauthn_error_empty=Sinun täytyy asettaa nimi tälle avaimelle.
|
||||
|
@ -228,22 +228,22 @@ report_message = Jos uskot tämän olevan Forgejon virhe, etsi ongelmia <a href=
|
|||
app_desc=Kivuton, itsehostattu Git-palvelu
|
||||
install=Helppo asentaa
|
||||
platform=Alustariippumaton
|
||||
platform_desc=Forgejo on mahdollista suorittaa vapaissa käyttöjärjestelmissä kuten Linux ja FreeBSD, ja se toimii eri suoritinarkkitehtuureilla. Valitse omasi!
|
||||
platform_desc=Forgejo on mahdollista suorittaa Linuxin ja FreeBSD:n kaltaisissa vapaissa käyttöjärjestelmissä, ja se toimii eri suoritinarkkitehtuureilla. Valitse omasi!
|
||||
lightweight=Kevyt
|
||||
lightweight_desc=Forgejolla on vähäiset vähimmäisvaatimukset, joten se toimii jopa halvassa Raspberry Pi:ssä. Säästä koneesi energiaa!
|
||||
license=Avoin lähdekoodi
|
||||
license_desc=Mene ja lataa <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Liity tekemään <a target="_blank" rel="noopener noreferrer" href="%[2]s">projektista</a> entistäkin parempi. Älä ujostele avustamista!
|
||||
install_desc = <a target="_blank" rel="noopener noreferrer" href="%[1]s">Suorita alustallesi sopiva binääritiedosto</a>, <a target="_blank" rel="noopener noreferrer" href="%[2]s">kontita se</a>, tai <a target="_blank" rel="noopener noreferrer" href="%[3]s">hanki se paketoituna</a>.
|
||||
install_desc = <a target="_blank" rel="noopener noreferrer" href="%[1]s">Suorita alustallesi tarkoitettu binääritiedosto</a>, <a target="_blank" rel="noopener noreferrer" href="%[2]s">kontita se</a>, tai <a target="_blank" rel="noopener noreferrer" href="%[3]s">hanki se paketoituna</a>.
|
||||
|
||||
[install]
|
||||
install=Asennus
|
||||
title=Aloitusasetukset
|
||||
docker_helper=Jos ajat Forgejoa Dockerin sisällä, lue <a target="_blank" rel="noopener noreferrer" href="%s">ohjeet</a> ennen minkään asetuksen muuttamista.
|
||||
docker_helper=Jos suoritat Forgejoa kontitettuna, lue <a target="_blank" rel="noopener noreferrer" href="%s">ohjeet</a>, ennen kuin muutat yhtäkään asetusta.
|
||||
require_db_desc=Forgejo tarvitsee toimiakseen MySQL-, PostgreSQL-, SQLite3- tai TiDB- (MySQL-protokolla) tietokannan.
|
||||
db_title=Tietokannan asetukset
|
||||
db_type=Tietokannan tyyppi
|
||||
host=Isäntä
|
||||
user=Käyttäjätunnus
|
||||
user=Käyttäjänimi
|
||||
password=Salasana
|
||||
db_name=Tietokannan nimi
|
||||
db_schema=Skeema
|
||||
|
@ -259,8 +259,8 @@ err_empty_db_path=SQLite3-tietokannan polku ei voi olla tyhjä.
|
|||
no_admin_and_disable_registration=Et voi kytkeä rekisteröintiä pois luomatta sitä ennen ylläpitotiliä.
|
||||
err_empty_admin_password=Ylläpitäjän salasana ei voi olla tyhjä.
|
||||
err_empty_admin_email=Ylläpitäjän sähköpostiosoite ei voi olla tyhjä.
|
||||
err_admin_name_is_reserved=Ylläpitäjän käyttäjätunnus on virheellinen; käyttäjätunnus on varattu
|
||||
err_admin_name_is_invalid=Ylläpitäjän käyttäjätunnus on virheellinen
|
||||
err_admin_name_is_reserved=Ylläpitäjän käyttäjänimi on virheellinen; käyttäjänimi on varattu
|
||||
err_admin_name_is_invalid=Ylläpitäjän käyttäjänimi on virheellinen
|
||||
|
||||
general_title=Yleiset asetukset
|
||||
app_name=Instanssin otsikko
|
||||
|
@ -286,7 +286,7 @@ smtp_addr=SMTP-isäntä
|
|||
smtp_port=SMTP-portti
|
||||
smtp_from=Lähetä sähköpostit osoitteella
|
||||
smtp_from_helper=Sähköpostiosoite, jota Forgejo käyttää. Kirjoita pelkkä sähköpostiosoite tai "Nimi” <email@example.com> -muodossa.
|
||||
mailer_user=SMTP-käyttäjätunnus
|
||||
mailer_user=SMTP-käyttäjänimi
|
||||
mailer_password=SMTP-salasana
|
||||
register_confirm=Vaadi sähköpostinvahvistus rekisteröinnin edellytykseksi
|
||||
mail_notify=Ota sähköposti-ilmoitukset käyttöön
|
||||
|
@ -326,7 +326,7 @@ default_keep_email_private.description=Piilota oletusarvoisesti uusien käyttäj
|
|||
default_enable_timetracking=Ota ajanseuranta oletusarvoisesti käyttöön
|
||||
default_enable_timetracking.description=Salli ajanseuranta-ominaisuuden käyttöönotto oletuksena uusille tietovarastoille.
|
||||
no_reply_address=Piilotetun sähköpostin toimialue
|
||||
no_reply_address_helper=Verkkotunnuksen nimi käyttäjille, joilla on piilotettu sähköpostiosoite. Esimerkiksi käyttäjätunnus 'joe' kirjataan Git-palveluun nimellä 'joe@noreply.example.org' jos piilotetun sähköpostiosoitteen arvoksi on asetettu 'noreply.example.org'.
|
||||
no_reply_address_helper=Verkkotunnuksen nimi käyttäjille, joilla on piilotettu sähköpostiosoite. Esimerkiksi käyttäjänimi 'joe' kirjataan Git-palveluun nimellä 'joe@noreply.example.org' jos piilotetun sähköpostiosoitteen arvoksi on asetettu 'noreply.example.org'.
|
||||
password_algorithm=Salasanan hajautusalgoritmi
|
||||
enable_update_checker_helper_forgejo = Se tarkistaa väliajoin uusia Forgejo-versioita tutkimalla TXT DNS -tietueen osoitteesta release.forgejo.org .
|
||||
invalid_admin_setting = Ylläpitotilin asetukset eivät kelpaa: %v
|
||||
|
@ -354,7 +354,7 @@ smtp_from_invalid = "Lähetä sähköpostit osoitteella"-osoite on epäkelvollin
|
|||
err_admin_name_pattern_not_allowed = Ylläpitäjän käyttäjänimi on epäkelpo, se vastaa varattua kaavaa
|
||||
|
||||
[home]
|
||||
uname_holder=Käyttäjätunnus tai sähköpostiosoite
|
||||
uname_holder=käyttäjänimi tai sähköpostiosoite
|
||||
password_holder=Salasana
|
||||
switch_dashboard_context=Vaihda kojelaudan kontekstia
|
||||
my_repos=Tietovarastot
|
||||
|
@ -410,10 +410,10 @@ remember_me=Muista tämä laite
|
|||
forgot_password_title=Unohtuiko salasana
|
||||
forgot_password=Unohtuiko salasana?
|
||||
sign_up_now=Tarvitsetko tilin? Rekisteröidy nyt.
|
||||
confirmation_mail_sent_prompt=Uusi varmistussähköposti on lähetetty osoitteeseen <b>%s</b>. Tarkista sähköpostisi ja seuraa saamaasi linkkiä seuraavan %s aikana viimeistelläksesi rekisteröinnin. Mikäli annettu sähköpostiosoite on väärin, voit kirjautua sisään ja pyytää uutta varmistussähköpostia toiseen osoitteeseen.
|
||||
confirmation_mail_sent_prompt=Uusi vahvistussähköposti on lähetetty osoitteeseen <b>%s</b>. Tarkista sähköpostisi ja seuraa saamaasi linkkiä seuraavan %s aikana viimeistelläksesi rekisteröinnin. Mikäli annettu sähköpostiosoite on väärin, voit kirjautua sisään ja pyytää uutta vahvistussähköpostia toiseen osoitteeseen.
|
||||
must_change_password=Vaihda salasanasi
|
||||
allow_password_change=Vaadi käyttäjää vaihtamaan salasanansa (suositeltava)
|
||||
reset_password_mail_sent_prompt=Varmistussähköposti on lähetetty osoitteeseen <b>%s</b>. Tarkista sähköpostisi ja seuraa annettua linkkiä seuraavan %s aikana saadaksesi tilin palauttamisen valmiiksi.
|
||||
reset_password_mail_sent_prompt=Vahvistussähköposti on lähetetty osoitteeseen <b>%s</b>. Tarkista sähköpostisi ja seuraa annettua linkkiä seuraavan %s aikana saadaksesi tilin palauttamisen valmiiksi.
|
||||
active_your_account=Aktivoi tilisi
|
||||
account_activated=Tili on aktivoitu
|
||||
prohibit_login=Tili on jäädytetty
|
||||
|
@ -431,7 +431,7 @@ verify=Vahvista
|
|||
scratch_code=Kertakäyttökoodi
|
||||
use_scratch_code=Käytä kertakäyttökoodia
|
||||
twofa_scratch_used=Olet käyttänyt kertakäyttökoodisi. Sinut on uudelleenohjattu kaksivaiheisen kirjautumisen asetussivulle, jotta voit kytkeä sen pois tai luoda uuden kertakäyttökoodin.
|
||||
twofa_passcode_incorrect=Salasanasi on väärä. Jos olet hukannut laitteesi, käytäthän kertakäyttökoodia sisäänkirjautumiseen.
|
||||
twofa_passcode_incorrect=Pääsykoodi on väärä. Jos olet hukannut laitteesi, käytäthän kertakäyttökoodia sisäänkirjautumiseen.
|
||||
twofa_scratch_token_incorrect=Kertakäyttökoodisi on virheellinen.
|
||||
login_userpass=Kirjaudu sisään
|
||||
tab_openid=OpenID
|
||||
|
@ -440,7 +440,7 @@ oauth_signup_title=Viimeistele uusi tili
|
|||
oauth_signup_submit=Viimeistele tili
|
||||
oauth_signin_tab=Linkitä olemassa olevaan tiliin
|
||||
oauth_signin_title=Kirjaudu sisään valtuuttaaksesi linkitetyn tilin
|
||||
oauth_signin_submit=Yhdistä tiliin
|
||||
oauth_signin_submit=Linkitä tili
|
||||
oauth.signin.error.access_denied=Valtuutuspyyntö on evätty.
|
||||
openid_connect_submit=Yhdistä
|
||||
openid_connect_title=Yhdistä olemassa olevaan tiliin
|
||||
|
@ -483,7 +483,7 @@ prohibit_login_desc = Tilisi käyttö instanssin kanssa on estetty. Ota yhteytt
|
|||
|
||||
[mail]
|
||||
view_it_on=Näytä %s
|
||||
link_not_working_do_paste=Eikö linkki toimi? Yritä kopioida ja liittää se selaimesi osoitepalkkiin.
|
||||
link_not_working_do_paste=Eikö linkki toimi? Kopioi ja liitä se selaimesi osoiteriville.
|
||||
hi_user_x=Hei <b>%s</b>,
|
||||
|
||||
activate_account=Ole hyvä ja aktivoi tilisi
|
||||
|
@ -491,7 +491,7 @@ activate_account=Ole hyvä ja aktivoi tilisi
|
|||
activate_email=Vahvista sähköpostiosoitteesi
|
||||
|
||||
register_notify=Tervetuloa %s-palveluun
|
||||
register_notify.text_2=Voit nyt kirjautua tilillesi käyttäjätunnuksella: %s
|
||||
register_notify.text_2=Voit nyt kirjautua tilillesi käyttäjänimellä: %s
|
||||
|
||||
reset_password=Palauta käyttäjätili
|
||||
reset_password.title=%s, olet pyytänyt tilisi palauttamista
|
||||
|
@ -517,7 +517,7 @@ removed_security_key.subject = Turva-avain on poistettu
|
|||
removed_security_key.text_1 = Turva-avain "%[1]s" on poistettu tililtäsi.
|
||||
team_invite.text_2 = Napsauta seuraavaa linkkiä liittyäksesi tiimiin:
|
||||
activate_account.text_1 = Hei <b>%[1]s</b>, kiitos kun rekisteröidyit palveluun %[2]s!
|
||||
activate_account.text_2 = Aktivoidaksesi tilin, napsauta alla olevaa linkkiä aikaikkunan <b>%s</b> sisällä:
|
||||
activate_account.text_2 = Aktivoi tilisi napsauttamalla alla olevaa linkkiä aikaikkunan <b>%s</b> sisällä:
|
||||
totp_disabled.subject = TOTP on poistettu käytöstä
|
||||
primary_mail_change.subject = Ensisijainen sähköpostiosoitteesi on vaihdettu
|
||||
admin.new_user.user_info = Käyttäjätiedot
|
||||
|
@ -570,7 +570,7 @@ modify=Päivitä
|
|||
confirm = Vahvista
|
||||
|
||||
[form]
|
||||
UserName=Käyttäjätunnus
|
||||
UserName=Käyttäjänimi
|
||||
RepoName=Tietovaraston nimi
|
||||
Email=Sähköpostiosoite
|
||||
Password=Salasana
|
||||
|
@ -601,14 +601,14 @@ captcha_incorrect=CAPTCHA-koodi on virheellinen.
|
|||
password_not_match=Salasanat eivät täsmää.
|
||||
lang_select_error=Valitse kieli listalta.
|
||||
|
||||
username_been_taken=Käyttäjätunnus on jo varattu.
|
||||
username_been_taken=Käyttäjänimi on jo varattu.
|
||||
repo_name_been_taken=Tietovaraston nimi on jo käytössä.
|
||||
repository_force_private=Pakotettu yksityisyys käytössä: yksityisiä tietovarastoja ei voida muuttaa julkisiksi.
|
||||
org_name_been_taken=Organisaation nimi on jo käytössä.
|
||||
team_name_been_taken=Tiimin nimi on jo varattu.
|
||||
email_been_used=Sähköpostiosoite on jo käytössä.
|
||||
email_invalid=Sähköpostiosoite on virheellinen.
|
||||
username_password_incorrect=Käyttäjätunnus tai salasana on virheellinen.
|
||||
username_password_incorrect=Käyttäjänimi tai salasana on virheellinen.
|
||||
password_lowercase_one=Ainakin yksi pieni kirjan
|
||||
password_uppercase_one=Ainakin yksi iso kirjain
|
||||
password_digit_one=Ainakin yksi numero
|
||||
|
@ -663,6 +663,10 @@ org_still_own_repo = Organisaatio omistaa yhden tai useamman tietovaraston. Pois
|
|||
org_still_own_packages = Organisaatio omistaa yhden tai useamman paketin. Poista ne ensin.
|
||||
team_no_units_error = Salli pääsy vähintään yhteen tietovaraston osioon.
|
||||
repository_files_already_exist.adopt_or_delete = Tässä tietovarastossa on jo tiedostoja. Omaksu ne itsellesi tai poista ne.
|
||||
username_change_not_local_user = Ei-paikallisten käyttäjien ei sallita vaihtaa käyttäjänimeä.
|
||||
admin_cannot_delete_self = Et voi poistaa itseäsi, kun olet ylläpitäjä. Poista ensin ylläpito-oikeudet itseltäsi.
|
||||
username_claiming_cooldown = Käyttäjänimeä ei voi ottaa käyttöön, koska siihen kohdistuva suojaamisjakso ei ole vielä päättynyt. Käyttäjänimen voi ottaa käyttöön %[1]s.
|
||||
email_domain_is_not_allowed = Käyttäjän sähköpostiosoitteen <b>%s</b> verkkotunnus on ristiriidassa EMAIL_DOMAIN_ALLOWLIST:in tai EMAIL_DOMAIN_BLOCKLIST:in kanssa. Varmista, että olen asettanut sähköpostiosoitteen oikein.
|
||||
|
||||
|
||||
[user]
|
||||
|
@ -694,11 +698,11 @@ unblock = Poista esto
|
|||
following_one = %d seurataan
|
||||
block_user.detail = Huomaa, että käyttäjän estämisellä on muita vaikutuksia, kuten:
|
||||
show_on_map = Näytä paikka kartalla
|
||||
form.name_chars_not_allowed = Käyttäjätunnus "%s" sisältää virheellisiä merkkejä.
|
||||
form.name_chars_not_allowed = Käyttäjänimi "%s" sisältää virheellisiä merkkejä.
|
||||
follow_blocked_user = Et voi seurata tätä käyttäjää, koska olet estänyt kyseisen käyttäjän tai kyseinen käyttäjä on estänyt sinut.
|
||||
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.
|
||||
form.name_reserved = Käyttäjänimi "%s" on varattu.
|
||||
form.name_pattern_not_allowed = Kaava "%s" ei ole sallittu käyttäjänimessä.
|
||||
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>.
|
||||
watched = Tarkaillut tietovarastot
|
||||
|
@ -727,7 +731,7 @@ organization=Organisaatiot
|
|||
webauthn=Kaksivaiheinen todennus (Turva-avaimet)
|
||||
|
||||
public_profile=Julkinen profiili
|
||||
password_username_disabled=Ei-paikalliset käyttäjät eivät voi muuttaa käyttäjätunnustaan. Ole hyvä ja ota yhteyttä sivuston ylläpitäjään saadaksesi lisätietoa.
|
||||
password_username_disabled=Ei-paikalliset käyttäjät eivät voi muuttaa käyttäjänimeään. Ota yhteys sivuston ylläpitoon saadaksesi lisätietoa.
|
||||
full_name=Koko nimi
|
||||
website=Verkkosivusto
|
||||
location=Sijainti
|
||||
|
@ -736,14 +740,14 @@ update_profile=Päivitä profiili
|
|||
update_language=Vaihda kieli
|
||||
update_language_success=Kieli on päivitetty.
|
||||
update_profile_success=Profiilisi on päivitetty.
|
||||
change_username=Käyttäjätunnuksesi on muutettu.
|
||||
change_username=Käyttäjänimesi on muutettu.
|
||||
continue=Jatka
|
||||
cancel=Peruuta
|
||||
language=Kieli
|
||||
ui=Teema
|
||||
hidden_comment_types=Piilotetut kommenttityypit
|
||||
comment_type_group_reference=Viittaus
|
||||
comment_type_group_label=Tunniste
|
||||
comment_type_group_label=Nimilappu
|
||||
comment_type_group_milestone=Merkkipaalu
|
||||
comment_type_group_assignee=Osoitettu henkilölle
|
||||
comment_type_group_title=Otsikko
|
||||
|
@ -888,10 +892,10 @@ twofa_enrolled=Tiliisi on otettu käyttöön kaksivaiheinen todennus. Ota kertak
|
|||
|
||||
webauthn_nickname=Nimimerkki
|
||||
|
||||
manage_account_links=Yhdistetyt tilit
|
||||
manage_account_links=Linkitetyt tilit
|
||||
manage_account_links_desc=Nämä ulkoiset tilit on linkitetty Forgejo-tiliisi.
|
||||
link_account=Yhdistä tili
|
||||
remove_account_link=Poista yhdistetty tili
|
||||
link_account=Linkitä tili
|
||||
remove_account_link=Poista linkitetty tili
|
||||
remove_account_link_desc=Linkitetyn tilin poistaminen peruuttaa pääsyn Forgejo-tiliisi linkitetyn tilin kautta. Jatketaanko?
|
||||
remove_account_link_success=Linkitetty tili on poistettu.
|
||||
|
||||
|
@ -968,14 +972,14 @@ webauthn_alternative_tip = Saatat haluta määrittää lisätodennusmenetelmän.
|
|||
twofa_disable = Poista kaksivaiheinen todennus käytöstä
|
||||
twofa_disable_desc = Kaksivaiheisen todennuksen poistaminen asettaa tilisi aiempaa suurempaan uhkaan. Jatketaanko?
|
||||
update_language_not_found = Kieli "%s" ei ole käytettävissä.
|
||||
change_username_prompt = Huomio: Käyttäjätunnuksen vaihtaminen muuttaa myös tilisi URL-osoitteen.
|
||||
change_username_prompt = Huomio: Käyttäjänimen vaihtaminen muuttaa myös tilisi URL-osoitteen.
|
||||
oauth2_client_secret_hint = Tätä salaisuutta ei näytetä uudelleen, kun olet poistunut sivulta tai päivittänyt sivun. Varmista, että olet ottanut salaisuuden talteen.
|
||||
blocked_since = Estetty %s lähtien
|
||||
user_unblock_success = Käyttäjän esto on poistettu.
|
||||
oauth2_redirect_uris = Uudelleenohjaus-URI:t. Käytä uutta riviä (newline) jokaista URI:a kohden.
|
||||
oauth2_client_secret = Asiakkaan salaisuus
|
||||
verify_ssh_key_success = SSH-avain "%s" on vahvistettu.
|
||||
change_username_redirect_prompt = Vanha käyttäjätunnus uudelleenohjaa, kunnes joku muu ottaa käyttäjätunnuksen käyttönsä.
|
||||
change_username_redirect_prompt = Vanha käyttäjänimi uudelleenohjaa, kunnes joku muu ottaa käyttäjänimen käyttöönsä.
|
||||
uploaded_avatar_is_too_big = Lähetetyn tiedoston koko (%d KiB) ylittää enimmäiskoon (%d KiB).
|
||||
ssh_key_been_used = Tämä SSH-avain on jo lisätty palvelimelle.
|
||||
verify_gpg_key_success = GPG-avain "%s" on vahvistettu.
|
||||
|
@ -1023,9 +1027,16 @@ keep_pronouns_private = Näytä pronominit vain tunnistautuneille käyttäjille
|
|||
keep_pronouns_private.description = Tämä piilottaa pronominisi käyttäjiltä, jotka eivät ole kirjautuneet sisään.
|
||||
comment_type_group_issue_ref = Ongelmaviittaus
|
||||
twofa_scratch_token_regenerated = Kertakäyttöinen palautusavaimesi on nyt %s. Talleta se turvalliseen sijaintiin, koska sitä ei näytetä uudelleen.
|
||||
change_username_redirect_prompt.with_cooldown.few = Vanha käyttäjätunnus on kenen tahansa saatavilla %[1]d päivän suojaamisjakson jälkeen. Voit palauttaa käyttäjätunnuksen itsellesi suojaamisjakson aikana.
|
||||
change_username_redirect_prompt.with_cooldown.few = Vanha käyttäjänimi on kenen tahansa saatavilla %[1]d päivän suojaamisjakson jälkeen. Voit palauttaa käyttäjänimen itsellesi suojaamisjakson aikana.
|
||||
additional_repo_units_hint_description = Näytä "Ota lisää käyttöön"-vihje tietovarastoissa, missä kaikki saatavilla olevat yksiköt eivät ole käytössä.
|
||||
change_username_redirect_prompt.with_cooldown.one = Vanha käyttäjätunnus on kenen tahansa saatavilla %[1]d päivän suojaamisjakson jälkeen. Voit palauttaa käyttäjätunnuksen itsellesi suojaamisjakson aikana.
|
||||
change_username_redirect_prompt.with_cooldown.one = Vanha käyttäjänimi on kenen tahansa saatavilla %[1]d päivän suojaamisjakson jälkeen. Voit palauttaa käyttäjänimen itsellesi suojaamisjakson aikana.
|
||||
gpg_key_matched_identities = Vastaavat identiteetit:
|
||||
delete_token_success = Pääsypoletti on poistettu. Sitä käyttävillä sovelluksilla ei ole enää pääsyä tilillesi.
|
||||
ssh_externally_managed = Tämän käyttäjän SSH-avainta hallitaan ulkoisesti
|
||||
passcode_invalid = Virheellinen pääsykoodi. Yritä uudelleen.
|
||||
then_enter_passcode = Kirjoita sovelluksessa näkyvä pääsykoodi:
|
||||
gpg_key_matched_identities_long = Tähän avaimeen upotetut identiteetit vastaavat tämän käyttäjän seuraavia aktivoituja sähköpostiosoitteita. Kommitit, jotka vastaavat näitä sähköpostiosoitteita, voidaan vahvistaa tällä avaimella.
|
||||
twofa_failed_get_secret = Salaisuuden saaminen epäonnistui.
|
||||
|
||||
[repo]
|
||||
owner=Omistaja
|
||||
|
@ -1048,8 +1059,8 @@ download_tar=Lataa TAR.GZ
|
|||
repo_desc=Kuvaus
|
||||
repo_lang=Kieli
|
||||
repo_gitignore_helper=Valitse .gitignore-mallit
|
||||
issue_labels=Tunnisteet
|
||||
issue_labels_helper=Valitse tunnistejoukko
|
||||
issue_labels=Nimilaput
|
||||
issue_labels_helper=Valitse nimilappujoukko
|
||||
license=Lisenssi
|
||||
license_helper=Valitse lisenssitiedosto
|
||||
readme=README
|
||||
|
@ -1074,14 +1085,14 @@ template.git_hooks=Git-koukut
|
|||
template.webhooks=Webkoukut
|
||||
template.topics=Aiheet
|
||||
template.avatar=Profiilikuva
|
||||
template.issue_labels=Ongelmatunnisteet
|
||||
template.issue_labels=Ongelmanimilaput
|
||||
|
||||
|
||||
|
||||
migrate_items=Migraation kohteet
|
||||
migrate_items_wiki=Wiki
|
||||
migrate_items_milestones=Merkkipaalut
|
||||
migrate_items_labels=Tunnisteet
|
||||
migrate_items_labels=Nimilaput
|
||||
migrate_items_issues=Ongelmat
|
||||
migrate_items_pullrequests=Vetopyynnöt
|
||||
migrate_items_releases=Julkaisut
|
||||
|
@ -1118,7 +1129,7 @@ issues=Ongelmat
|
|||
pulls=Vetopyynnöt
|
||||
project_board=Projektit
|
||||
packages=Paketit
|
||||
labels=Tunnisteet
|
||||
labels=Nimilaput
|
||||
|
||||
milestones=Merkkipaalut
|
||||
commits=Kommitit
|
||||
|
@ -1129,7 +1140,7 @@ released_this=julkaisi tämän
|
|||
file_raw=Raaka
|
||||
file_history=Historia
|
||||
file_view_raw=Näytä raaka
|
||||
file_permalink=Pysyvä linkki
|
||||
file_permalink=Pysyväislinkki
|
||||
|
||||
video_not_supported_in_browser=Selaimesi ei tue HTML5:n video-tagia.
|
||||
audio_not_supported_in_browser=Selaimesi ei tue HTML5:n audio-tagia.
|
||||
|
@ -1201,9 +1212,9 @@ issues.desc=Ongelmien, tehtävien ja merkkipaalujen hallinta.
|
|||
issues.filter_assignees=Suodata käyttäjiä
|
||||
issues.filter_milestones=Suodata merkkipaalu
|
||||
issues.new=Uusi ongelma
|
||||
issues.new.labels=Tunnisteet
|
||||
issues.new.no_label=Ei tunnisteita
|
||||
issues.new.clear_labels=Tyhjennä tunnisteet
|
||||
issues.new.labels=Nimilaput
|
||||
issues.new.no_label=Ei nimilappuja
|
||||
issues.new.clear_labels=Tyhjennä nimilaput
|
||||
issues.new.projects=Projektit
|
||||
issues.new.no_items=Ei kohteita
|
||||
issues.new.milestone=Merkkipaalu
|
||||
|
@ -1218,11 +1229,11 @@ issues.choose.open_external_link=Avaa
|
|||
issues.choose.blank=Oletus
|
||||
issues.no_ref=Haaraa/tagia ei määritelty
|
||||
issues.create=Luo ongelma
|
||||
issues.new_label=Uusi tunniste
|
||||
issues.new_label_placeholder=Tunnisteen nimi
|
||||
issues.new_label=Uusi nimilappu
|
||||
issues.new_label_placeholder=Nimilapun nimi
|
||||
issues.new_label_desc_placeholder=Kuvaus
|
||||
issues.create_label=Luo tunniste
|
||||
issues.label_templates.helper=Valitse tunnisteen esiasetus
|
||||
issues.create_label=Luo nimilappu
|
||||
issues.label_templates.helper=Valitse nimilapun 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`
|
||||
|
@ -1232,9 +1243,9 @@ issues.deleted_project=`(poistettu)`
|
|||
issues.self_assign_at=`itse otti tämän käsittelyyn %s`
|
||||
issues.change_title_at=`muutti otsikon <b><strike>%s</strike></b> otsikoksi <b>%s</b> %s`
|
||||
issues.delete_branch_at=`poisti haaran <b>%s</b> %s`
|
||||
issues.filter_label=Tunniste
|
||||
issues.filter_label_exclude=`Käytä <code>alt</code> + <code>klikkaus/rivinvaihto</code> poissulkeaksesi tunnisteita`
|
||||
issues.filter_label_no_select=Kaikki tunnisteet
|
||||
issues.filter_label=Nimilappu
|
||||
issues.filter_label_exclude=`Käytä <code>alt</code> + <code>napsautus/rivinvaihto</code> poissulkeaksesi nimilappuja`
|
||||
issues.filter_label_no_select=Kaikki nimilaput
|
||||
issues.filter_milestone=Merkkipaalu
|
||||
issues.filter_project=Projekti
|
||||
issues.filter_assignee=Käsittelijä
|
||||
|
@ -1252,15 +1263,15 @@ issues.filter_sort.recentupdate=Äskettäin päivitetty
|
|||
issues.filter_sort.leastupdate=Kauiten aikaa sitten päivitetty
|
||||
issues.filter_sort.mostcomment=Eniten kommentoidut
|
||||
issues.filter_sort.leastcomment=Vähiten kommentoidut
|
||||
issues.filter_sort.nearduedate=Lähin määräpäivä
|
||||
issues.filter_sort.farduedate=Kaukaisin määräpäivä
|
||||
issues.filter_sort.nearduedate=Lähin eräpäivä
|
||||
issues.filter_sort.farduedate=Kaukaisin eräpäivä
|
||||
issues.filter_sort.moststars=Eniten tähtiä
|
||||
issues.filter_sort.feweststars=Vähiten tähtiä
|
||||
issues.filter_sort.mostforks=Eniten forkattu
|
||||
issues.filter_sort.fewestforks=Vähiten forkattu
|
||||
issues.action_open=Avaa
|
||||
issues.action_close=Sulje
|
||||
issues.action_label=Tunniste
|
||||
issues.action_label=Nimilappu
|
||||
issues.action_milestone=Merkkipaalu
|
||||
issues.action_milestone_no_select=Ei merkkipaalua
|
||||
issues.action_assignee=Osoitettu henkilölle
|
||||
|
@ -1291,15 +1302,15 @@ issues.role.member=Jäsen
|
|||
issues.edit=Muokkaa
|
||||
issues.cancel=Peruuta
|
||||
issues.save=Tallenna
|
||||
issues.label_title=Tunnisteen nimi
|
||||
issues.label_title=Nimi
|
||||
issues.label_description=Kuvaus
|
||||
issues.label_color=Tunnisteen väri
|
||||
issues.label_count=%d tunnistetta
|
||||
issues.label_color=Väri
|
||||
issues.label_count=%d nimilappua
|
||||
issues.label_open_issues=%d avointa ongelmaa/vetopyyntöä
|
||||
issues.label_edit=Muokkaa
|
||||
issues.label_delete=Poista
|
||||
issues.label_modify=Muokkaa tunnistetta
|
||||
issues.label_deletion=Poista tunniste
|
||||
issues.label_modify=Muokkaa nimilappua
|
||||
issues.label_deletion=Poista nimilappu
|
||||
issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä
|
||||
issues.label.filter_sort.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
|
||||
issues.label.filter_sort.by_size=Pienin koko
|
||||
|
@ -1334,13 +1345,13 @@ issues.add_time_hours=Tuntia
|
|||
issues.add_time_minutes=Minuuttia
|
||||
issues.add_time_sum_to_small=Aikaa ei syötetty.
|
||||
issues.time_spent_from_all_authors=`Käytetty kokonaisaika: %s`
|
||||
issues.due_date=Määräpäivä
|
||||
issues.due_date=Eräpäivä
|
||||
issues.push_commit_1=lisäsi %d kommitin %s
|
||||
issues.push_commits_n=lisäsi %d kommittia %s
|
||||
issues.due_date_form=vvvv-kk-pp
|
||||
issues.due_date_form_edit=Muokkaa
|
||||
issues.due_date_form_remove=Poista
|
||||
issues.due_date_not_set=Määräpäivää ei ole asetettu.
|
||||
issues.due_date_not_set=Eräpäivää ei ole asetettu.
|
||||
issues.due_date_overdue=Myöhässä
|
||||
issues.dependency.title=Riippuvuudet
|
||||
issues.dependency.issue_no_dependencies=Riippuvuuksia ei ole asetettu.
|
||||
|
@ -1394,13 +1405,13 @@ pulls.can_auto_merge_desc=Tämä vetopyyntö voidaan yhdistää automaattisesti.
|
|||
|
||||
milestones.new=Uusi merkkipaalu
|
||||
milestones.closed=Suljettu %s
|
||||
milestones.no_due_date=Ei määräpäivää
|
||||
milestones.no_due_date=Ei eräpäivää
|
||||
milestones.open=Avaa uudelleen
|
||||
milestones.close=Sulje
|
||||
milestones.create=Luo merkkipaalu
|
||||
milestones.title=Otsikko
|
||||
milestones.desc=Kuvaus
|
||||
milestones.due_date=Määräpäivä (valinnainen)
|
||||
milestones.due_date=Eräpäivä (valinnainen)
|
||||
milestones.clear=Tyhjennä
|
||||
milestones.edit=Muokkaa merkkipaalua
|
||||
milestones.cancel=Peruuta
|
||||
|
@ -1411,7 +1422,7 @@ milestones.filter_sort.least_issues=Vähiten ongelmia
|
|||
|
||||
|
||||
wiki=Wiki
|
||||
wiki.welcome=Tervetuloa Wikiin.
|
||||
wiki.welcome=Tervetuloa wikiin.
|
||||
wiki.welcome_desc=Wikissä voit kirjoittaa ja jakaa dokumentaatiota käyttäjien kesken.
|
||||
wiki.create_first_page=Luo ensimmäinen sivu
|
||||
wiki.page=Sivu
|
||||
|
@ -1522,9 +1533,9 @@ settings.update_githook=Päivitä koukku
|
|||
settings.payload_url=Kohde-URL
|
||||
settings.http_method=HTTP-menetelmä
|
||||
settings.secret=Salaisuus
|
||||
settings.slack_username=Käyttäjätunnus
|
||||
settings.slack_username=Käyttäjänimi
|
||||
settings.slack_icon_url=Kuvakkeen URL-osoite
|
||||
settings.discord_username=Käyttäjätunnus
|
||||
settings.discord_username=Käyttäjänimi
|
||||
settings.event_desc=Laukaisu päällä:
|
||||
settings.event_send_everything=Kaikki tapahtumat
|
||||
settings.event_choose=Mukautetut tapahtumat…
|
||||
|
@ -1544,7 +1555,7 @@ settings.event_issues=Muokkaus
|
|||
settings.event_issues_desc=Ongelma avattu, suljettu, avattu uudelleen tai muokattu.
|
||||
settings.event_issue_assign=Toimeksianto
|
||||
settings.event_issue_assign_desc=Ongelma osoitettu tai osoitus poistettu.
|
||||
settings.event_issue_label_desc=Ongelmatunnisteet lisätty tai poistettu.
|
||||
settings.event_issue_label_desc=Ongelmanimilaput lisätty tai poistettu.
|
||||
settings.event_issue_milestone_desc=Merkkipaalu lisätty, poistettu tai muokattu.
|
||||
settings.event_issue_comment_desc=Ongelman kommentti luotu, muokattu tai poistettu.
|
||||
settings.event_header_pull_request=Vetopyyntöjen tapahtumat
|
||||
|
@ -1744,7 +1755,7 @@ release.detail = Julkaisun tiedot
|
|||
diff.hide_file_tree = Piilota tiedostopuu
|
||||
issues.role.owner_helper = Tämä käyttäjä on tämän tietovaraston omistaja.
|
||||
issues.all_title = Kaikki
|
||||
issues.label_archived_filter = Näytä arkistoidut tunnisteet
|
||||
issues.label_archived_filter = Näytä arkistoidut nimilaput
|
||||
pulls.close = Sulje vetopyyntö
|
||||
branch.already_exists = Haara nimellä "%s" on jo olemassa.
|
||||
diff.show_file_tree = Näytä tiedostopuu
|
||||
|
@ -1793,7 +1804,7 @@ milestones.deletion_success = Merkkipaalu on poistettu.
|
|||
project = Projektit
|
||||
pulls.delete.title = Poistetaanko tämä vetopyyntö?
|
||||
activity.title.issues_1 = %d ongelma
|
||||
contributors.contribution_type.filter_label = Avustuksen tyyppi:
|
||||
contributors.contribution_type.filter_label = Kontribuution tyyppi:
|
||||
settings.protected_branch.delete_rule = Poista sääntö
|
||||
settings.archive.success = Tietovarasto arkistoitiin onnistuneesti.
|
||||
diff.comment.placeholder = Jätä kommentti
|
||||
|
@ -1897,7 +1908,7 @@ pulls.expand_files = Laajenna kaikki tiedostot
|
|||
issues.content_history.delete_from_history = Poista historiasta
|
||||
milestones.filter_sort.name = Nimi
|
||||
issues.filter_milestone_all = Kaikki merkkipaalut
|
||||
issues.filter_label_select_no_label = Ei tunnistetta
|
||||
issues.filter_label_select_no_label = Ei nimilappua
|
||||
projects.column.set_default = Aseta oletukseksi
|
||||
projects.edit_success = Projekti "%s" on päivitetty.
|
||||
desc.sha256 = SHA256
|
||||
|
@ -1942,7 +1953,7 @@ settings.branches.update_default_branch = Päivitä oletushaara
|
|||
settings.transfer.success = Tietovaraston siirto onnistui.
|
||||
settings.transfer_abort = Peru siirto
|
||||
settings.sync_mirror = Synkronoi nyt
|
||||
settings.mirror_settings.docs.doc_link_title = Kuinka peilaan tietovarastot?
|
||||
settings.mirror_settings.docs.doc_link_title = Kuinka peilaan tietovarastoja?
|
||||
tag.create_tag_operation = Luo tagi
|
||||
branch.rename = Nimeä haara "%s" uudelleen
|
||||
branch.download = Lataa haara "%s"
|
||||
|
@ -2016,9 +2027,9 @@ migrate.git.description = Suorita tietovaraston migraatio mistä tahansa Git-pal
|
|||
migrate.gitlab.description = Tee migraatio gitlab.comista tai muista GitLab-instansseista.
|
||||
migrate.gitea.description = Tee migraatio gitea.comista tai muista Gitea-instansseista.
|
||||
repo_gitignore_helper_desc = Valitse mitä tiedostoja ei seurata yleisimpien kielten mallipohjista. Tyypilliset artefaktit, joita eri kielten koostamistyökalut tuottavat, lisätään .gitignore-tiedostoon oletusarvoisesti.
|
||||
milestones.filter_sort.latest_due_date = Kaukaisin määräpäivä
|
||||
milestones.filter_sort.latest_due_date = Kaukaisin eräpäivä
|
||||
license_helper_desc = Lisenssi määrää, mitä muut voivat ja eivät voi tehdä koodillasi. Etkö ole varma, mikä lisenssi soveltuu projektillesi? Lue <a target="_blank" rel="noopener noreferrer" href="%s">ohje lisenssin valinnasta</a>.
|
||||
milestones.filter_sort.earliest_due_data = Lähin määräpäivä
|
||||
milestones.filter_sort.earliest_due_data = Lähin eräpäivä
|
||||
issues.filter_type.reviewed_by_you = Katselmoitu toimestasi
|
||||
settings.units.overview = Yleisnäkymä
|
||||
settings.remove_team_success = Tiimin pääsy tietovarastoon on poistettu.
|
||||
|
@ -2185,21 +2196,21 @@ transfer.no_permission_to_accept = Sinulla ei ole oikeutta hyväksyä tätä sii
|
|||
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.add_label = lisäsi nimilapun %s %s
|
||||
issues.due_date_added = lisäsi erä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.add_labels = lisäsi nimilaput %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 tietovarastoon.
|
||||
settings.event_pull_request_label = Tunnisteet
|
||||
issues.due_date_remove = poisti määräpäivän %s %s
|
||||
settings.event_issue_label = Tunnisteet
|
||||
settings.event_pull_request_label = Nimilaput
|
||||
issues.due_date_remove = poisti eräpäivän %s %s
|
||||
settings.event_issue_label = Nimilaput
|
||||
settings.authorization_header = Authorization-otsake
|
||||
diff.has_escaped = Tällä rivillä on piilotettuja Unicode-merkkejä
|
||||
issues.max_pinned = Et voi kiinnittää enempää ongelmia
|
||||
|
@ -2211,8 +2222,8 @@ 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.label_templates.title = Lataa nimilapun esiasetus
|
||||
issues.label_deletion_desc = Nimilapun 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
|
||||
|
@ -2223,8 +2234,8 @@ 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.label_templates.use = Käytä nimilapun esiasetusta
|
||||
issues.label_deletion_success = Nimilappu on poistettu.
|
||||
issues.cancel_tracking = Hylkää
|
||||
issues.choose.get_started = Aloitetaan
|
||||
settings.event_fork = Forkkaus
|
||||
|
@ -2239,7 +2250,7 @@ pulls.no_merge_wip = Tätä vetopyyntöä ei voida yhdistää, koska se on merki
|
|||
pulls.clear_merge_message = Tyhjennä yhdistämisviesti
|
||||
activity.title.prs_merged_by = %s yhdisti %s
|
||||
settings.protect_status_check_patterns_desc = Syötä kaavat määrittääksesi, mitkä tilatarkistukset on läpäistävä, ennen kuin haarat voidaan yhdistää tätä sääntöä vastaavaan haaraan. Jokainen rivi määrittää kaavan. Kaavat eivät saa olla tyhjiä.
|
||||
adopt_search = Syötä käyttäjänimi etsiäksesi omaksumattomia tietovarastoja (jätä tyhjäksi löytääksesi kaikki)
|
||||
adopt_search = Syötä käyttäjänimi etsiäksesi omaksumattomia tietovarastoja… (jätä tyhjäksi löytääksesi kaikki)
|
||||
pulls.cmd_instruction_merge_warning = <b>Varoitus:</b> Asetusta ”Tunnista manuaalinen yhdistäminen automaattisesti” ei ole otettu käyttöön tässä tietovarastossa. Sinun on merkittävä tämä vetopyyntö manuaalisesti yhdistetyksi jälkikäteen.
|
||||
pulls.cmd_instruction_merge_desc = Yhdistä muutokset ja päivitä Forgejossa.
|
||||
pulls.cannot_auto_merge_desc = Tätä vetopyyntöä ei voida yhdistää automaattisesti ristiriitojen vuoksi.
|
||||
|
@ -2316,13 +2327,13 @@ migrate_options_lfs = Tee migraatio LFS-tiedostoille
|
|||
migrate_options_lfs_endpoint.label = LFS-päätepiste
|
||||
commits.browse_further = Selaa kauemmas
|
||||
issues.filter_projects = Suodata projekti
|
||||
issues.filter_labels = Suodata tunniste
|
||||
issues.filter_labels = Suodata nimilappu
|
||||
commits.no_commits = Ei yhteisiä kommitteja. "%s" ja "%s" omaavat täysin eri historiat.
|
||||
projects.column.deletion_desc = Projektin sarakkeen poistaminen siirtää kaikki siihen liittyvät ongelmat oletussarakkeeseen. Jatketaanko?
|
||||
issues.del_time = Poista tämä aikaloki
|
||||
migrated_from_fake = Suoritettu migraatio lähteestä %[1]s
|
||||
migrate.migrate = Tee migraatio lähteestä %s
|
||||
migrate.migrating_labels = Suoritetaan tunnisteiden migraatiota
|
||||
migrate.migrating_labels = Suoritetaan nimilappujen migraatiota
|
||||
file_view_rendered = Näytä renderöitynä
|
||||
editor.invalid_commit_mail = Virheellinen sähköposti kommitin luomista varten.
|
||||
sync_fork.branch_behind_one = Tämä haara on %[1]d kommitin jäljessä %[2]s
|
||||
|
@ -2360,7 +2371,7 @@ pulls.is_ancestor = Tämä haara on jo sisällytetty kohdehaaraan. Yhdistettäv
|
|||
pulls.blocked_by_rejection = Tämä vetopyyntö sisältää virallisen katselmoijan vaatimisia muutoksia.
|
||||
pulls.status_checks_success = Kaikki tarkistukset onnistuivat
|
||||
pulls.agit_explanation = Luotu käyttäen AGit-työnkulkua. AGit antaa avustajien ehdottaa muutoksia käyttämällä "git push" ilman, että uutta forkkia tai uutta haaraa luodaan.
|
||||
milestones.invalid_due_date_format = Määräpäivän muodon tulee olla "yyyy-mm-dd".
|
||||
milestones.invalid_due_date_format = Eräpäivän muodon tulee olla "yyyy-mm-dd".
|
||||
wiki.original_git_entry_tooltip = Näytä alkuperäinen Git-tiedosto sen sijaan, että ystävällistä linkkiä käytetään.
|
||||
pulls.blocked_by_approvals = Tällä vetopyynnöllä ei ole riittävästi hyväksyntöjä. %d/%d hyväksyntää myönnetty.
|
||||
pulls.status_checks_hide_all = Piilota kaikki tarkistukset
|
||||
|
@ -2393,7 +2404,7 @@ settings.trust_model.collaborator.desc = Tämän tietovaraston avustajien kelvol
|
|||
settings.confirm_wiki_branch_rename = Nimeä uudelleen wikin haara
|
||||
settings.event_pull_request_assign = Toimeksianto
|
||||
settings.event_pull_request_assign_desc = Vetopyynnön toimeksianto luotu tai toimeksiannon osoitus poistettu.
|
||||
settings.event_pull_request_label_desc = Vetopyynnön tunnisteita lisätty tai poistettu.
|
||||
settings.event_pull_request_label_desc = Vetopyynnön nimilappuja lisätty tai poistettu.
|
||||
settings.active = Aktiivinen
|
||||
settings.packagist_api_token = API-poletti
|
||||
settings.protect_whitelist_committers = Sallittujen listalla rajoitettu työntö
|
||||
|
@ -2431,7 +2442,7 @@ settings.event_pull_request_review_request_desc = Vetopyynnön katselmointi pyyd
|
|||
settings.event_pull_request_merge = Vetopyynnön yhdistäminen
|
||||
settings.protect_approvals_whitelist_enabled = Rajoita hyväksynnät vain sallittujen käyttäjien tai tiimien listoilla oleviin
|
||||
settings.packagist_package_url = Packagist-paketin URL-osoite
|
||||
settings.packagist_username = Packagist-käyttäjätunnus
|
||||
settings.packagist_username = Packagist-käyttäjänimi
|
||||
settings.sourcehut_builds.manifest_path = Koontimanifestin polku
|
||||
settings.event_pull_request_sync_desc = Haara päivitetty automaattisesti kohdehaaralla.
|
||||
settings.trust_model.committer = Kommitoija
|
||||
|
@ -2539,6 +2550,33 @@ form.reach_limit_of_creation_n = Omistajan %d tietovaraston rajoitus on jo täyn
|
|||
form.string_too_long = Merkkijono on pidempi kuin %d merkkiä.
|
||||
mirror_address_protocol_invalid = Määritetty URL-osoite on virheellinen. Vain http(s):// tai git:// -sijainteja voi käyttää peilaukseen.
|
||||
form.name_pattern_not_allowed = Kaava "%s" ei ole sallittu tietovaraston nimessä.
|
||||
migrate_options_lfs_endpoint.description.local = Paikallinen palvelinpolku on myös tuettu.
|
||||
pulls.showing_only_single_commit = Näytetään vain kommitin %[1]s muutokset
|
||||
pulls.invalid_merge_option = Et voi käyttää tätä yhdistämisvalintaa tälle vetopyynnölle.
|
||||
pulls.squash_merge_pull_request = Luo squash-kommitti
|
||||
issues.label_templates.info = Nimilappuja ei ole. Luo nimilappu napsauttamalla "Uusi nimilappu" tai käytä nimilapun esiasetusta:
|
||||
issues.label_archive = Arkistoi nimilappu
|
||||
mirror_lfs_desc = Aktivoi LFS-datan peilaaminen.
|
||||
editor.directory_is_a_file = Hakemiston nimi "%s" on jo käytössä tiedoston nimenä tässä tietovarastossa.
|
||||
projects.desc = Hallitse ongelmia ja vetoja projektitauluilla.
|
||||
ext_issues = Ulkoiset ongelmat
|
||||
issues.label_archive_tooltip = Arkistoidut nimilaput on suljettu pois ehdotuksista oletusarvoisesti, kun haku suoritetaan nimilapulla.
|
||||
issues.ref_reopened_from = `<a href="%[3]s">avasi uudelleen tämän ongelman %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.ref_closed_from = `<a href="%[3]s">sulki tämän ongelman %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
migrate_options_lfs_endpoint.description = Migraatio yrittää käyttää Git-etätietovarastoasi <a target="_blank" rel="noopener noreferrer" href="%s">LFS-palvelimen määrittämiseen</a>. Voit määrittää omavalintaisen päätepisteen, jos tietovarastosi LFS-data on talletettu jonnekin muualle.
|
||||
settings.mirror_settings.docs.pull_mirror_instructions = Vetopeilin määrittämiseksi konsultoi:
|
||||
issues.archived_label_description = (Arkistoitu) %s
|
||||
editor.filename_is_a_directory = Tiedoston nimi "%s" on jo käytössä hakemiston nimenä tässä tietovarastossa.
|
||||
pulls.fast_forward_only_merge_pull_request = Pelkkä fast-forward
|
||||
pulls.rebase_merge_commit_pull_request = Rebase, luo sitten yhdistämiskommitti
|
||||
pulls.rebase_merge_pull_request = Rebase, sitten fast-forward
|
||||
admin.enabled_flags = Tässä tietovarastossa käytössä olevat liput:
|
||||
admin.update_flags = Päivitä liput
|
||||
admin.failed_to_replace_flags = Tietovaraston lippujen korvaaminen epäonnistui
|
||||
admin.flags_replaced = Tietovaraston liput korvattu
|
||||
admin.manage_flags = Hallitse lippuja
|
||||
editor.file_is_a_symlink = `"%s" on symbolinen linkki. Symbolisia linkkejä ei voi muokata selainkäyttöliittymän editorissa`
|
||||
rss.must_be_on_branch = Sinun täytyy olla haarassa saadaksesi RSS-syötteen.
|
||||
|
||||
|
||||
|
||||
|
@ -2667,6 +2705,7 @@ teams.add_nonexistent_repo = Tietovarasto, jota yrität lisätä, ei ole olemass
|
|||
teams.repos.none = Tällä tiimillä ei ole pääsyä tietovarastoihin.
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.one = Vanha organisaation nimi on kenen tahansa saatavilla %[1]d päivän suojaamisjakson jälkeen. Voit palauttaa organisaation nimen itsellesi suojaamisjakson aikana.
|
||||
settings.change_orgname_redirect_prompt.with_cooldown.few = Vanha organisaation nimi on kenen tahansa saatavilla %[1]d päivän suojaamisjakson jälkeen. Voit palauttaa organisaation nimen itsellesi suojaamisjakson aikana.
|
||||
teams.all_repositories_helper = Tiimillä on pääsy kaikkiin tietovarastoihin. Tämän valitseminen <strong>lisää kaikki olemassa olevat</strong> tietovarastot tiimiin.
|
||||
|
||||
[admin]
|
||||
dashboard=Kojelauta
|
||||
|
@ -2720,7 +2759,7 @@ dashboard.gc_times=Roskienkeruuajat
|
|||
|
||||
users.user_manage_panel=Käyttäjätilien hallinta
|
||||
users.new_account=Luo käyttäjätili
|
||||
users.name=Käyttäjätunnus
|
||||
users.name=Käyttäjänimi
|
||||
users.full_name=Koko nimi
|
||||
users.activated=Aktivoitu
|
||||
users.admin=Ylläpito
|
||||
|
@ -2854,7 +2893,7 @@ config.db_config=Tietokannan asetukset
|
|||
config.db_type=Tyyppi
|
||||
config.db_host=Isäntä
|
||||
config.db_name=Nimi
|
||||
config.db_user=Käyttäjätunnus
|
||||
config.db_user=Käyttäjänimi
|
||||
config.db_ssl_mode=SSL
|
||||
config.db_path=Polku
|
||||
|
||||
|
@ -3357,6 +3396,8 @@ alt.repository = Tietovaraston tiedot
|
|||
arch.version.replaces = Korvaa
|
||||
debian.repository = Tietovaraston tiedot
|
||||
conda.registry = Määritä tämä rekisteri Conda-tietovarastoksi <code>.condarc</code>-tiedostossa:
|
||||
container.labels = Nimilaput
|
||||
settings.link.description = Jos linkität paketin tietovarastoon, paketti listataan tietovaraston pakettilistalla.
|
||||
|
||||
[secrets]
|
||||
creation.failed = Salaisuuden lisääminen epäonnistui.
|
||||
|
@ -3371,6 +3412,7 @@ deletion.description = Salaisuuden poistaminen on pysyvä toimenpide, eikä sit
|
|||
deletion.success = Salaisuus on poistettu.
|
||||
description = Salaisuudet välitetään tietyille toimenpiteille, eikä niitä voi muuten lukea.
|
||||
creation.name_placeholder = kirjoinkoolla ei merkitystä, vain aakkosnumeerisia merkkejä ja alaviivoja, ei voi alkaa GITEA_ tai GITHUB_
|
||||
creation.value_placeholder = Syötä mitä tahansa sisältöä. Tyhjätila alussa ja lopussa jätetään huomiotta.
|
||||
|
||||
[actions]
|
||||
runners.name=Nimi
|
||||
|
@ -3405,7 +3447,7 @@ workflow.dispatch.input_required = Arvo syötteelle "%s" vaadittu.
|
|||
runners.status.active = Aktiivinen
|
||||
runs.no_workflows.documentation = Katso lisätietoja Forgejo Actions -ohjelmistosta <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaatiosta</a>.
|
||||
variables.description = Muuttujat asetetaan tietyille toiminnoille eikä niitä voida lukea muutoin.
|
||||
runners.labels = Tunnisteet
|
||||
runners.labels = Nimilaput
|
||||
runners.delete_runner_failed = Testinajajan poisto epäonnistui
|
||||
runners.delete_runner_header = Varmista testinajajan poisto
|
||||
runners.task_list.status = Tila
|
||||
|
@ -3418,7 +3460,7 @@ runners.task_list.no_tasks = Tehtäviä ei ole vielä määritelty.
|
|||
runners.last_online = Viimeisin käynnissäoloajankohta
|
||||
runners.runner_title = Testinajaja
|
||||
runners.task_list.done_at = Valmistunut ajankohtana
|
||||
runs.no_matching_online_runner_helper = Testiajajaa tunnisteella %s ei löytynyt
|
||||
runs.no_matching_online_runner_helper = Testiajajaa nimilapulla %s ei löytynyt
|
||||
runs.no_results = Ei tuloksia.
|
||||
runners.delete_runner = Poista testinajaja
|
||||
variables.deletion.description = Muuttujan poistaminen on lopullista, eikä sitä voi perua. Jatketaanko?
|
||||
|
@ -3465,6 +3507,7 @@ runners.status.offline = Ei-verkkotilassa
|
|||
runs.no_job_without_needs = Työnkulun tulee sisältää vähintään yksi työ ilman riippuvuuksia.
|
||||
runs.no_runs = Työnkululla ei ole vielä suorituksia.
|
||||
variables.not_found = Muuttujaa ei löytynyt.
|
||||
runs.no_workflows.help_write_access = Etkö tiedä, miten aloittaa Forgejo Actionsin käyttö? Lue <a target="_blank" rel="noopener noreferrer" href="%s">pikaopas</a> kirjoittaaksesi ensimmäisen työnkulun, sen jälkeen <a target="_blank" rel="noopener noreferrer" href="%s">määritä Forgejo-ajaja</a> suorittamaan asettamiasi töitä.
|
||||
|
||||
|
||||
|
||||
|
@ -3520,4 +3563,12 @@ issues.read = <b>Lue:</b> Lue ja luo ongelmia ja kommentteja.
|
|||
releases.read = <b>Lue:</b> Katsele ja lataa julkaisuja.
|
||||
pulls.read = <b>Lue:</b> Vetopyyntöjen lukeminen ja luominen.
|
||||
ext_issues = Pääsy ulkoisen ongelmanseurannan linkkiin. Käyttöoikeuksia hallitaan ulkoisesti.
|
||||
ext_wiki = Pääsy ulkoisen wikin linkkiin. Käyttöoikeuksia hallitaan ulkoisesti.
|
||||
ext_wiki = Pääsy ulkoisen wikin linkkiin. Käyttöoikeuksia hallitaan ulkoisesti.
|
||||
projects.read = <b>Lue:</b> Pääsy tietovaraston projektitauluille.
|
||||
wiki.write = <b>Kirjoita:</b> Luo, päivitä ja poista integroidun wikin sivuja.
|
||||
|
||||
[markup]
|
||||
filepreview.truncated = Esikatselu on typistetty
|
||||
|
||||
[translation_meta]
|
||||
test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :) :) :)
|
|
@ -38,9 +38,9 @@ logo = Logo
|
|||
sign_in = Mag-sign in
|
||||
sign_in_with_provider = Mag-sign in gamit ang %s
|
||||
sign_in_or = o
|
||||
sign_out = Mag-Sign Out
|
||||
sign_out = Mag-sign out
|
||||
sign_up = Magrehistro
|
||||
link_account = Mag-link ng Account
|
||||
link_account = Mag-link ng account
|
||||
template = Template
|
||||
tracked_time_summary = Buod ng mga nakasubaybay na oras base sa filter ng listahan ng isyu
|
||||
webauthn_sign_in = Pindutin ang button ng iyong security key. Kung walang button ang iyong security key, ilagay muli.
|
||||
|
@ -701,7 +701,7 @@ ssh_gpg_keys = Mga SSH / GPG key
|
|||
applications = Mga Aplikasyon
|
||||
orgs = Ipamahala ang mga organisasyon
|
||||
repos = Mga Repositoryo
|
||||
delete = Burahin ang Account
|
||||
delete = Burahin ang account
|
||||
twofa = Authentikasyong two-factor (TOTP)
|
||||
account_link = Mga naka-link na account
|
||||
uid = UID
|
||||
|
@ -1674,10 +1674,10 @@ issues.new_label = Bagong label
|
|||
issues.label_templates.title = Mag-load ng isang label preset
|
||||
issues.new.clear_milestone = I-clear ang milestone
|
||||
issues.new.open_milestone = Mga bukas na milestone
|
||||
issues.filter_milestones = I-filter ang Milestone
|
||||
issues.filter_projects = I-filter ang Proyekto
|
||||
issues.filter_labels = I-filter ang Label
|
||||
issues.filter_reviewers = I-filter ang Tagasuri
|
||||
issues.filter_milestones = I-filter ang milestone
|
||||
issues.filter_projects = I-filter ang proyekto
|
||||
issues.filter_labels = I-filter ang label
|
||||
issues.filter_reviewers = I-filter ang tagasuri
|
||||
issues.remove_labels = tinanggal ang mga label na %s %s
|
||||
issues.add_remove_labels = idinagdag ang %s at tinanggal ang %s na mga label %s
|
||||
issues.add_milestone_at = `idinagdag ito sa <b>%s</b> na milestone %s`
|
||||
|
@ -1688,7 +1688,7 @@ issues.add_label = idinagdag ang %s na label %s
|
|||
issues.add_labels = idinagdag ang mga label na %s %s
|
||||
issues.remove_label = tinanggal ang %s na label %s
|
||||
issues.desc = Ayusin ang mga ulat ng bug, gawain, at milestone.
|
||||
issues.filter_assignees = I-filter ang Mangangasiwa
|
||||
issues.filter_assignees = I-filter ang mangangasiwa
|
||||
issues.new.labels = Mga label
|
||||
issues.new.no_label = Walang mga label
|
||||
issues.new.clear_labels = I-clear ang mga label
|
||||
|
@ -1893,7 +1893,7 @@ settings.collaboration.owner = May-ari
|
|||
pulls.showing_only_single_commit = Ipinapakita lamang ang mga pagbago ng commit na %[1]s
|
||||
comments.edit.already_changed = Hindi maimbak ang mga pagbabago sa komento. Mukhang nabago na ng ibang tagagamit ang nilalaman. Mangyaring i-refresh ang pahina at subukang baguhin muli upang maiwasang ma-overwrite ang kanilang pagbago
|
||||
milestones.completeness = <strong>%d%%</strong> nakumpleto
|
||||
wiki.welcome = Maligayang pagdating sa Wiki.
|
||||
wiki.welcome = Maligayang pagdating sa wiki.
|
||||
wiki.create_first_page = Gawin ang unang pahina
|
||||
pulls.switch_comparison_type = Ilipat ang uri ng pagkumpara
|
||||
settings.collaboration.read = Basahin
|
||||
|
@ -2278,7 +2278,7 @@ settings.add_collaborator = Magdagdag ng katulong
|
|||
settings.add_collaborator_duplicate = Nadagdag na ang tagatulong na ito sa repositoryo.
|
||||
settings.add_collaborator_blocked_our = Hindi madagdag ang tagatulong, dahil hinarang siya ng may-ari ng repositoryo.
|
||||
settings.add_collaborator_blocked_them = Hindi madagdag ang tagatulong, dahil hinarang niya ang may-ari ng repositoryo.
|
||||
settings.collaborator_deletion = Tanggalin ang Tagatulong
|
||||
settings.collaborator_deletion = Tanggalin ang tagatulong
|
||||
settings.team_not_in_organization = Ang koponan ay hindi nasa katulad na organisasyon sa repositoryo
|
||||
settings.teams = Mga Koponan
|
||||
settings.add_team_success = May access na ang koponan sa repositoryo na ito.
|
||||
|
@ -3568,8 +3568,8 @@ npm.details.tag = Tag
|
|||
swift.install = Idagdag ang package sa iyong <code>Package.swift</code> na file:
|
||||
vagrant.install = Para magdagdag ng Vagrant box, patakbuhin ang sumusunod na command:
|
||||
settings.link = I-link ang package na ito sa repository
|
||||
settings.link.select = Pumili ng Repositoryo
|
||||
settings.link.button = I-update ang Link ng Repositoryo
|
||||
settings.link.select = Pumili ng repositoryo
|
||||
settings.link.button = I-update ang link ng repositoryo
|
||||
settings.link.error = Nabigong i-update ang link ng repositoryo.
|
||||
settings.delete = Burahin ang package
|
||||
owner.settings.cargo.initialize = I-initialize ang index
|
||||
|
@ -3712,7 +3712,7 @@ runners.reset_registration_token = I-reset ang token ng pagrehistro
|
|||
runners.status.offline = Offline
|
||||
workflow.dispatch.invalid_input_type = Hindi wastong input type "%s".
|
||||
runners.task_list.commit = Commit
|
||||
runners.task_list.done_at = Natapos Sa
|
||||
runners.task_list.done_at = Natapos sa
|
||||
runners.reset_registration_token_success = Matagumpay na na-reset ang token ng pagrehistro ng runner
|
||||
workflow.dispatch.input_required = Kumailangan ng value para sa input na "%s".
|
||||
workflow.dispatch.warn_input_limit = Pinapakita lamang ang unang %d na mga input.
|
||||
|
@ -3830,7 +3830,7 @@ deletion.success = Natanggal na ang lihim.
|
|||
deletion.failed = Nabigong tanggalin ang lihim.
|
||||
creation.failed = Nabigong idagdag ang lihim.
|
||||
deletion = Tanggalin ang lihim
|
||||
creation = Idagdag ang Lihim
|
||||
creation = Idagdag ang lihim
|
||||
description = Ang mga sikreto ay ipapasa sa ilang mga aksyon at hindi mababasa kung hindi.
|
||||
none = Wala pang mga sikreto sa ngayon.
|
||||
creation.name_placeholder = case-insensitive, alphanumeric character o underscore lamang, hindi dapat magsimula sa GITEA_ o GITHUB_
|
||||
|
@ -3844,7 +3844,7 @@ filepreview.truncated = Na-truncate ang preview
|
|||
filepreview.lines = Mga linya %[1]d hanggang %[2]d sa %[3]s
|
||||
|
||||
[projects]
|
||||
deleted.display_name = Binurang Proyekto
|
||||
deleted.display_name = Binurang proyekto
|
||||
type-2.display_name = Proyekto ng repositoryo
|
||||
type-1.display_name = Indibidwal na proyekto
|
||||
type-3.display_name = Proyekto ng organisasyon
|
||||
|
|
|
@ -2044,7 +2044,7 @@ ext_wiki=Wiki externe
|
|||
ext_wiki.desc=Lier un wiki externe.
|
||||
|
||||
wiki=Wiki
|
||||
wiki.welcome=Bienvenue sur le Wiki.
|
||||
wiki.welcome=Bienvenue sur le wiki.
|
||||
wiki.welcome_desc=Le wiki vous permet d'écrire ou de partager de la documentation avec vos collaborateurs.
|
||||
wiki.desc=Écrire et partager de la documentation avec vos collaborateurs.
|
||||
wiki.create_first_page=Créer la première page
|
||||
|
@ -2913,6 +2913,13 @@ pulls.editable_explanation = Cette pull request peut être éditée par les main
|
|||
sync_fork.branch_behind_one = Cette branche a %[1]d commits de retard sur %[2]s
|
||||
sync_fork.branch_behind_few = Cettte branche a %[1]d commits de retard sur %[2]s
|
||||
sync_fork.button = Sync
|
||||
settings.event_action_failure = Échec
|
||||
settings.event_action_recover = Récupérer
|
||||
settings.event_action_success = Réussite
|
||||
settings.event_header_action = Événements d'exécution d'action
|
||||
settings.event_action_success_desc = L'exécution de l'action a réussi.
|
||||
settings.event_action_failure_desc = L'exécution de l'action a échoué.
|
||||
settings.event_action_recover_desc = L'exécution de l'action a réussi après l'échec de la dernière exécution de l'action dans le même workflow.
|
||||
|
||||
[graphs]
|
||||
component_loading = Chargement %s…
|
||||
|
@ -2921,7 +2928,7 @@ component_loading_failed = Échec de chargement de %s
|
|||
component_loading_info = Cela peut prendre du temps…
|
||||
component_failed_to_load = Une erreur inattendue s'est produite.
|
||||
contributors.what = contributions
|
||||
code_frequency.what = fŕequence de code
|
||||
code_frequency.what = fréquence de code
|
||||
recent_commits.what = commits récents
|
||||
|
||||
|
||||
|
@ -4004,7 +4011,7 @@ variables.not_found = La variable n'a pas été trouvée.
|
|||
type-1.display_name=Projet personnel
|
||||
type-2.display_name=Projet du dépôt
|
||||
type-3.display_name=Projet de l'organisation
|
||||
deleted.display_name = Projet Supprimé
|
||||
deleted.display_name = Projet supprimé
|
||||
|
||||
[git.filemode]
|
||||
changed_filemode=%[1]s → %[2]s
|
||||
|
|
|
@ -190,6 +190,7 @@ table_modal.placeholder.header = Cabeceira
|
|||
link_modal.header = Engadir ligazón
|
||||
link_modal.url = Url
|
||||
link_modal.description = Descrición
|
||||
link_modal.paste_reminder = Consello: Coa URL no portapapeis, podes pegala directamente no editor para crear unha ligazón.
|
||||
|
||||
|
||||
[search]
|
||||
|
@ -225,6 +226,8 @@ app_desc = Um servizo Git autoxestionado e fácil de usar
|
|||
install = Fácil de instalar
|
||||
install_desc = Simplemente <a target="_blank" rel="noopener noreferrer" href="%[1]s">executa o binario</a> para a túa plataforma, envíao con <a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a> ou consígueo <a target="_blank" rel="noopener noreferrer" href="%[3]s">empaquetado</a>.
|
||||
license = Código aberto
|
||||
lightweight_desc = Forgejo precisa duns requerimentos mínimos e pode funcionar nunha Raspberry Pi barata. Aforra enerxía na túa máquina!
|
||||
lightweight = Lixeiro
|
||||
|
||||
[error]
|
||||
occurred = Ocorreu un erro
|
||||
|
@ -291,6 +294,7 @@ 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
|
||||
require_db_desc = Forgejo precisa MySQL, PostgreSQL, SQLite3 ou TiDB (protocolo MySQL).
|
||||
|
||||
[repo]
|
||||
sync_fork.branch_behind_few = Esta rama ten %d achegas por detrás de %s
|
||||
|
|
|
@ -8,7 +8,7 @@ sign_in=Accedi
|
|||
sign_in_or=o
|
||||
sign_out=Esci
|
||||
sign_up=Registrati
|
||||
link_account=Collega Profilo
|
||||
link_account=Collega profilo
|
||||
register=Registrati
|
||||
version=Versione
|
||||
powered_by=Gestito da %s
|
||||
|
@ -99,7 +99,7 @@ preview=Anteprima
|
|||
loading=Caricamento…
|
||||
|
||||
error=Errore
|
||||
error404=La pagina che stai cercando di raggiungere <strong>non esiste</strong> oppure <strong>non sei autorizzato</strong> a visualizzarla.
|
||||
error404=La pagina che stai cercando di raggiungere <strong>non esiste</strong>, <strong>è stata rimossa</strong> oppure <strong>non sei autorizzato</strong> a visualizzarla.
|
||||
|
||||
never=Mai
|
||||
|
||||
|
@ -3869,7 +3869,7 @@ changed_filemode = %[1]s → %[2]s
|
|||
|
||||
[search]
|
||||
type_tooltip = Tipo ricerca
|
||||
search = Cerca...
|
||||
search = Cerca…
|
||||
fuzzy = Approssimativa
|
||||
match = Precisa
|
||||
org_kind = Cerca organizzazioni...
|
||||
|
|
5
options/locale/locale_jbo.ini
Normal file
5
options/locale/locale_jbo.ini
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
|
||||
[common]
|
||||
home = zdani
|
|
@ -312,7 +312,7 @@ default_allow_create_organization=조직 생성 허용을 기본값으로 설정
|
|||
default_allow_create_organization.description=신규 사용자에게 기본적으로 조직 생성 권한을 부여합니다. 이 옵션이 꺼져있다면, 관리자가 신규 사용자에게 조직 생성 권한을 부여해야합니다.
|
||||
default_enable_timetracking=시간 기록 기능을 기본적으로 사용
|
||||
default_enable_timetracking.description=신규 저장소가 시간기록 기능을 기본적으로 사용할 수 있습니다.
|
||||
no_reply_address=가려진 이메일 도메인
|
||||
no_reply_address=숨겨진 이메일 도메인
|
||||
no_reply_address_helper=이메일을 가린 사용자에게 적용될 이메일 도메인입니다. 예를 들어, 사용자명 'joe'가 도메인'noreply.example.org'로 이메일을 가리면 Git에 'joe@noreply.example.org'로 로그인 하게 됩니다.
|
||||
db_schema_helper = 데이터베이스 기본값 ("공개")를 사용하려면 빈 칸으로 두세요.
|
||||
require_db_desc = Forgejo를 사용하려면 MySQL, PostgreSQL, SQLite3 또는 TiDB (MySQL 프로토콜) 이 설치되어 있어야 합니다.
|
||||
|
@ -330,6 +330,12 @@ app_slogan_helper = 인스턴스의 슬로건을 입력하세요. 비워두면
|
|||
reinstall_confirm_check_1 = app.ini의 SECRET_KEY로 암호화 되어있는 데이터를 잃을 수 있습니다: 2FA/OTP를 통해 로그인 할 수 없으며 & 미러가 제대로 작동하지 않게됩니다. app.ini 파일에 정확한 SECRET_KEY가 있는것이 확실하다면 체크하세요.
|
||||
run_user_helper = Forgejo를 구동하는 운영체제의 사용자명입니다. 이 사용자는 저장소 루트 경로에 접근권한이 있어야 합니다.
|
||||
reinstall_confirm_check_2 = 저장소와 설정에 재동기화가 요구될 수 있습니다. 이 박스에 체크하면 저장소의 훅과 authorized_key 들을 수동으로 재동기화해야 한다는 것을 인지한다는 것을 의미합니다. 저장소와 미러의 설정이 올바른지 확인하세요.
|
||||
password_algorithm = 암호 해시 알고리즘
|
||||
enable_update_checker = 업데이트 확인 활성화
|
||||
secret_key_failed = 비밀 키 생성 실패: %v
|
||||
env_config_keys = 환경 설정
|
||||
invalid_password_algorithm = 올바르지 않은 암호 해시 알고리즘
|
||||
invalid_db_table = 데이터베이스 테이블 "%s"이(가) 올바르지 않습니다: %v
|
||||
|
||||
[home]
|
||||
uname_holder=사용자명 또는 이메일 주소
|
||||
|
@ -347,6 +353,8 @@ search_repos=저장소 찾기..
|
|||
show_private=비공개
|
||||
|
||||
issues.in_your_repos=당신의 저장소에
|
||||
feed_of = "%s"의 피드
|
||||
filter = 다른 필터
|
||||
|
||||
[explore]
|
||||
repos=저장소
|
||||
|
|
|
@ -162,7 +162,7 @@ filter.not_archived = Nav arhivētas
|
|||
filter.is_fork = Atzarojumi
|
||||
filter.not_fork = Nav atzarojumi
|
||||
filter.is_mirror = Spoguļglabātavas
|
||||
filter.public = Atklātas
|
||||
filter.public = Publiskas
|
||||
filter.private = Privātas
|
||||
filter.clear = Notīrīt atlasi
|
||||
confirm_delete_artifact = Vai tiešām izdzēst artefaktu '%s'?
|
||||
|
@ -374,9 +374,9 @@ show_only_archived=Attēlot tikai arhivētos
|
|||
show_only_unarchived=Attēlot tikai nearhivētos
|
||||
|
||||
show_private=Privāts
|
||||
show_both_private_public=Rāda gan atklātās, gan privātās
|
||||
show_both_private_public=Rāda gan publiskās, gan privātās
|
||||
show_only_private=Attēlot tikai privātos
|
||||
show_only_public=Attēlo tikai atklātās
|
||||
show_only_public=Tiek rādītas tikai publiskās
|
||||
|
||||
issues.in_your_repos=Manās glabātavās
|
||||
|
||||
|
@ -690,7 +690,7 @@ email_domain_is_not_allowed = Lietotāja e-pasta adreses <b>%s</b> domēna vārd
|
|||
change_avatar=Mainīt profila attēlu…
|
||||
joined_on=Pievienojās %s
|
||||
repositories=Glabātavas
|
||||
activity=Atklāti notikumi
|
||||
activity=Publiskas darbības
|
||||
followers_few=%d sekotāji
|
||||
starred=Izlasei pievienotās glabātavas
|
||||
watched=Vērotās glabātavas
|
||||
|
@ -701,7 +701,7 @@ following_few=%d seko
|
|||
follow=Sekot
|
||||
unfollow=Pārtraukt sekot
|
||||
user_bio=Apraksts par sevi
|
||||
disabled_public_activity=Šis lietotājs ir atspējojis darbību redzamību visiem.
|
||||
disabled_public_activity=Šis lietotājs ir atspējojis darbību redzamību citiem.
|
||||
email_visibility.limited=E-pasta adrese ir redzama visiem autentificētajiem lietotājiem
|
||||
email_visibility.private=E-pasta adrese ir redzama tikai administratoriem
|
||||
show_on_map=Rādīt šo vietu kartē
|
||||
|
@ -749,7 +749,7 @@ organization=Apvienības
|
|||
uid=UID
|
||||
webauthn=Divpakāpju pieteikšanās (drošības atslēgas)
|
||||
|
||||
public_profile=Visiem pieejamais profils
|
||||
public_profile=Publiskais profils
|
||||
biography_placeholder=Pastāsti citiem mazliet par sevi! (Tiek atbalstīts Markdown)
|
||||
location_placeholder=Kopīgot savu aptuveno atrašanās vietu ar citiem
|
||||
profile_desc=Par Tevi
|
||||
|
@ -940,8 +940,8 @@ access_token_deletion_confirm_action=Dzēst
|
|||
access_token_deletion_desc=Pilnvaras izdzēšana atsauks lietotņu, kas to izmanto, piekļuvi kontam. Šo darbību nevar atsaukt. Turpināt?
|
||||
delete_token_success=Pilnvara tika izdzēsta. Lietotnēm, kas to izmanto, vairs nav piekļuves kontam.
|
||||
repo_and_org_access=Glabātavas un apvienības piekļuve
|
||||
permissions_public_only=Tikai atklātās
|
||||
permissions_access_all=Visas (atklātās, privātās un ierobežotās)
|
||||
permissions_public_only=Tikai publiskās
|
||||
permissions_access_all=Visas (publiskās, privātās un ierobežotās)
|
||||
select_permissions=Atlasīt atļaujas
|
||||
permission_no_access=Nav piekļuves
|
||||
permission_read=Lasīt
|
||||
|
@ -1035,14 +1035,14 @@ email_notifications.submit=Iestatīt e-pasta iestatījumus
|
|||
email_notifications.andyourown=Un manus paziņojumus
|
||||
|
||||
visibility=Lietotāja redzamība
|
||||
visibility.public=Atklāta
|
||||
visibility.public=Publiska
|
||||
visibility.public_tooltip=Redzams ikvienam
|
||||
visibility.limited=Ierobežota
|
||||
visibility.limited_tooltip=Redzams tikai lietotājiem, kuri ir pieteikušies
|
||||
visibility.private=Privāta
|
||||
visibility.private_tooltip=Redzams tikai apvienību, kurās pievienojies, dalībniekiem
|
||||
change_password = Mainīt paroli
|
||||
keep_activity_private.description = Tavas <a href="%s">atklātās darbības</a> būs redzamas tikai Tev un servera pārvaldītājiem.
|
||||
keep_activity_private.description = Tavas <a href="%s">publiskās darbības</a> būs redzamas tikai Tev un servera pārvaldītājiem.
|
||||
update_hints = Atjaunināt norādes
|
||||
update_hints_success = Norādes tika atjauninātas.
|
||||
user_block_success = Lietotājs tika sekmīgi liegts.
|
||||
|
@ -1083,7 +1083,7 @@ quota.applies_to_org = Uz apvienību attiecas zemāk esošās ierobežojuma kār
|
|||
quota.rule.no_limit = Neierobežots
|
||||
quota.sizes.all = Viss
|
||||
quota.sizes.repos.all = Glabātavas
|
||||
quota.sizes.repos.public = Atklātās glabātavas
|
||||
quota.sizes.repos.public = Publiskās glabātavas
|
||||
quota.sizes.repos.private = Privātās glabātavas
|
||||
regenerate_token = Izveidot no jauna
|
||||
access_token_regeneration = Izveidot piekļuves pilnvaru no jauna
|
||||
|
@ -1195,7 +1195,7 @@ transfer.no_permission_to_accept=Nav atļaujas pieņemt šo nodošanu.
|
|||
transfer.no_permission_to_reject=Nav atļaujas noraidīt šo nodošanu.
|
||||
|
||||
desc.private=Privāts
|
||||
desc.public=Atklāts
|
||||
desc.public=Publisks
|
||||
desc.template=Sagatave
|
||||
desc.internal=Iekšējs
|
||||
desc.archived=Arhivēts
|
||||
|
@ -1464,7 +1464,7 @@ commit.cherry-pick-content=Atlasīt zaru, uz kuru izlasīt:
|
|||
commitstatus.error=Kļūda
|
||||
commitstatus.failure=Atteice
|
||||
commitstatus.pending=Nav iesūtīts
|
||||
commitstatus.success=Pabeigts
|
||||
commitstatus.success=Sekmīgs
|
||||
|
||||
ext_issues=Ārēji pieteikumi
|
||||
ext_issues.desc=Saite uz ārējo problēmu sekotāju.
|
||||
|
@ -1515,7 +1515,7 @@ issues.filter_assignees=Atlasīt pēc atbildīgajiem
|
|||
issues.filter_milestones=Atlasīt pēc atskaites punkta
|
||||
issues.filter_projects=Atlasīt pēc projekta
|
||||
issues.filter_labels=Atlasīt pēc iezīmes
|
||||
issues.filter_reviewers=Atlasīt izskatītājus
|
||||
issues.filter_reviewers=Atlasīt pēc izskatītājiem
|
||||
issues.new=Jauns pieteikums
|
||||
issues.new.title_empty=Nosaukums nevar būt tukšs
|
||||
issues.new.labels=Iezīmes
|
||||
|
@ -2041,7 +2041,7 @@ ext_wiki=Ārēja vikivietne
|
|||
ext_wiki.desc=Ārējā vikivietne norāda uz ārējo vikivietnes adresi.
|
||||
|
||||
wiki=Vikivietne
|
||||
wiki.welcome=Laipni lūdzam vikivietnē.
|
||||
wiki.welcome=Laipni lūdzam vikivietnē!
|
||||
wiki.welcome_desc=Vikivietne ļauj rakstīt un kopīgot dokumentāciju ar līdzdalībniekiem.
|
||||
wiki.desc=Dokumentācijas rakstīšana un kopīgošana ar līdzdalībniekiem.
|
||||
wiki.create_first_page=Izveidot pirmo lapu
|
||||
|
@ -2769,7 +2769,7 @@ no_eol.text = Nav EOL
|
|||
size_format = %[1]s: %[2]s; %[3]s: %[4]s
|
||||
mirror_public_key = Publiskā SSH atslēga
|
||||
mirror_use_ssh.text = Izmantot SSH autentificēšanos
|
||||
mirror_use_ssh.helper = Forgejo spoguļos glabātavu ar Git un SSH un izveidos atslēgu pāri, kad tiks atlasīta šī iespēja. Jānodrošina, ka izveidotais atslēgu pāris ir pilnvarots aizgādāt mērķa glabātavā. Nevarēs izmantot pilnvarošanu ar paroli, kad šis tiek atlasīts.
|
||||
mirror_use_ssh.helper = Forgejo spoguļos glabātavu ar Git un SSH un izveidos atslēgu pāri, kad tiks atlasīta šī iespēja. Jānodrošina, ka izveidotā publiskāš atslēga ir pilnvarota aizgādāt mērķa glabātavā. Nevarēs izmantot pilnvarošanu ar paroli, kad šis tiek atlasīts.
|
||||
mirror_use_ssh.not_available = SSH autentificēšanās nav pieejama.
|
||||
mirror_denied_combination = Nevar izmantot autentificēšanos ar publiskās atslēgas un paroles apvienojumu.
|
||||
migrate.forgejo.description = Pārcelt datus no codeberg.org vai citiem Fogejo serveriem.
|
||||
|
@ -2959,7 +2959,7 @@ settings.location=Atrašanās vieta
|
|||
settings.permission=Tiesības
|
||||
settings.repoadminchangeteam=Glabātavas pārvaldītājs var pievienot un noņemt komandu piekļuvi
|
||||
settings.visibility=Redzamība
|
||||
settings.visibility.public=Atklāta
|
||||
settings.visibility.public=Publiska
|
||||
settings.visibility.limited=Ierobežota (redzama tikai lietotājiem, kuri ir pieteikušies)
|
||||
settings.visibility.limited_shortname=Ierobežota
|
||||
settings.visibility.private=Privāta (redzama tikai apvienības dalībniekiem)
|
||||
|
@ -3594,7 +3594,7 @@ self_check.database_collation_mismatch = Sagaidīt, ka datubāzē tiek izmantota
|
|||
self_check.database_fix_mysql = MySQL/MariaDB lietotāji var izmantot komandu "forgejo doctor convert", lai novērstu salīdzināšanas sarežģījumus, vai arī tos var pašrocīgi novērst ar "ALTER ... COLLATE ..." vaicājumiem.
|
||||
config.app_slogan = Servera sauklis
|
||||
config.allow_dots_in_usernames = Ļaut lietotājiem izmantot punktus savā lietotājvārdā. Neietekmē esošos kontus.
|
||||
users.restricted.description = Ļaut mijiedarbību tikai ar glabātavām un apvienībām, kurās šis lietotājs ir pievienots kā līdzdalībnieks. Tas neļauj piekļūt šī servera atklātajām glabātavām.
|
||||
users.restricted.description = Ļaut mijiedarbību tikai ar glabātavām un apvienībām, kurās šis lietotājs ir pievienots kā līdzdalībnieks. Tas neļauj piekļūt šī servera publiskajām glabātavām.
|
||||
dashboard.sync_tag.started = Uzsākta birku sinhronizēšana
|
||||
users.organization_creation.description = Ļaut jaunu apvienību izveidošanu.
|
||||
users.block.description = Liegt šī lietotāja mijiedarbību ar šo serveri caur tā kontu un neļaut pieteikšanos.
|
||||
|
@ -3925,7 +3925,7 @@ runners.task_list.run=Izpildījums
|
|||
runners.task_list.status=Stāvoklis
|
||||
runners.task_list.repository=Glabātava
|
||||
runners.task_list.commit=Iesūtījums
|
||||
runners.task_list.done_at=Beigu laiks
|
||||
runners.task_list.done_at=Pabeigts
|
||||
runners.edit_runner=Labot izpildītāju
|
||||
runners.update_runner=Atjaunināt izmaiņas
|
||||
runners.update_runner_success=Izpildītājs sekmīgi atjaunināts
|
||||
|
|
|
@ -1439,7 +1439,7 @@ issues.comment_manually_pull_merged_at = hett Kommitteren %[1]s in %[2]s %[3]s v
|
|||
issues.reopen_issue = Weer opmaken
|
||||
issues.closed_at = `hett deeses Gefall <a id="%[1]s" href="#%[1]s">%[2]s</a> dichtmaakt`
|
||||
issues.commit_ref_at = `hett deeses Gefall <a id="%[1]s" href="#%[1]s">%[2]s</a> vun eenem Kommitteren benöömt`
|
||||
issues.ref_closing_from = `<a href="%[3]s">hett deeses Gefall vun eenem Haalvörslag, wat ’t %[4]s dichtmaken word,</a> <a id="%[1]s" href="#%[1]s">%[2]s</a> <a href="%[3]s">benöömt</a>`
|
||||
issues.ref_closing_from = `<a href="%[3]s">hett deeses Gefall</a> <a id="%[1]s" href="#%[1]s">%[2]s</a> <a href="%[3]s"> vun eenem Haalvörslag, wat ’t %[4]s dichtmaken word, benöömt</a>`
|
||||
issues.ref_closed_from = `<a href="%[3]s">hett deeses Gefall %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a> <a href="%[3]s">dichtmaakt</a>`
|
||||
issues.ref_reopened_from = `<a href="%[3]s">hett deeses Gefall %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a> <a href="%[3]s">weer opmaakt</a>`
|
||||
issues.ref_from = `vun %[1]s`
|
||||
|
@ -2614,6 +2614,13 @@ archive.nocomment = Kommenteren gaht hier nich, denn dat Repositorium is archive
|
|||
sync_fork.button = Vernejen
|
||||
sync_fork.branch_behind_one = Deeser Twieg is %[1]d Kommitteren achter %[2]s
|
||||
sync_fork.branch_behind_few = Deeser Twieg is %[1]d Kommitterens achter %[2]s
|
||||
settings.event_action_failure = Fehlslagen
|
||||
settings.event_action_success = Daankregen
|
||||
settings.event_action_success_desc = Aktioons-Loop is all daankregen worden.
|
||||
settings.event_action_recover = Verhaalt
|
||||
settings.event_header_action = Aktioons-Loop-Vörfallen
|
||||
settings.event_action_failure_desc = Aktioons-Loop is as fehlslagen ennt.
|
||||
settings.event_action_recover_desc = Aktioons-Loop is daankregen worden, nadeem de leste Aktioons-Loop in de sülven Warkwies fehlslagen is.
|
||||
|
||||
[repo.permissions]
|
||||
code.read = <b>Lesen:</b> De Quelltext vun deesem Repositorium ankieken un klonen.
|
||||
|
|
|
@ -8,7 +8,7 @@ sign_in=Aanmelden
|
|||
sign_in_or=of
|
||||
sign_out=Uitloggen
|
||||
sign_up=Registreren
|
||||
link_account=Account Koppelen
|
||||
link_account=Account koppelen
|
||||
register=Registreren
|
||||
version=Versie
|
||||
powered_by=Mogelijk gemaakt door %s
|
||||
|
@ -2031,7 +2031,7 @@ settings.add_collaborator_success=De medewerker is toegevoegd.
|
|||
settings.add_collaborator_inactive_user=Kan geen inactieve gebruiker toevoegen als medewerker.
|
||||
settings.add_collaborator_duplicate=De collaborator is al toegevoegd aan deze repository.
|
||||
settings.delete_collaborator=Verwijder
|
||||
settings.collaborator_deletion=Verwijder medewerker
|
||||
settings.collaborator_deletion=Verwijder samenwerker
|
||||
settings.collaborator_deletion_desc=Het verwijderen van een collaborator zal hun toegang tot deze repository intrekken. Doorgaan?
|
||||
settings.remove_collaborator_success=De medewerker is verwijderd.
|
||||
settings.search_user_placeholder=Zoek gebruiker…
|
||||
|
@ -3914,7 +3914,7 @@ runners.task_list.no_tasks = Er is nog geen taak.
|
|||
runners.labels = Labels
|
||||
runners.last_online = Laatste online tijd
|
||||
runners.task_list.status = Status
|
||||
runners.task_list.done_at = Gedaan Op
|
||||
runners.task_list.done_at = Gedaan op
|
||||
runners.id = ID
|
||||
runs.actor = Acteur
|
||||
actions = Actions
|
||||
|
|
|
@ -1503,11 +1503,11 @@ projects.card_type.images_and_text=Imagens e texto
|
|||
projects.card_type.text_only=Somente texto
|
||||
|
||||
issues.desc=Organize relatórios de bugs, tarefas e marcos.
|
||||
issues.filter_assignees=Filtrar Atribuição
|
||||
issues.filter_milestones=Filtrar Marco
|
||||
issues.filter_projects=Filtrar Projeto
|
||||
issues.filter_labels=Filtrar Rótulo
|
||||
issues.filter_reviewers=Filtrar Revisor
|
||||
issues.filter_assignees=Filtrar atribuição
|
||||
issues.filter_milestones=Filtrar marco
|
||||
issues.filter_projects=Filtrar projeto
|
||||
issues.filter_labels=Filtrar rótulo
|
||||
issues.filter_reviewers=Filtrar revisor
|
||||
issues.new=Novo issue
|
||||
issues.new.title_empty=Título não pode ser em branco
|
||||
issues.new.labels=Etiquetas
|
||||
|
@ -2013,7 +2013,7 @@ ext_wiki=Wiki Externa
|
|||
ext_wiki.desc=Link para uma wiki externa.
|
||||
|
||||
wiki=Wiki
|
||||
wiki.welcome=Bem-vindo a wiki.
|
||||
wiki.welcome=Bem-vindo à wiki.
|
||||
wiki.welcome_desc=A wiki permite que você escreva e compartilhe a documentação com os colaboradores.
|
||||
wiki.desc=Escrever e compartilhar a documentação com os colaboradores.
|
||||
wiki.create_first_page=Criar a primeira página
|
||||
|
@ -3808,8 +3808,8 @@ swift.install2=e execute o seguinte comando:
|
|||
vagrant.install=Para adicionar uma Vagrant box, execute o seguinte comando:
|
||||
settings.link=Vincular este pacote a um repositório
|
||||
settings.link.description=Se você vincular um pacote a um repositório, o pacote será listado na lista de pacotes do repositório.
|
||||
settings.link.select=Selecionar Repositório
|
||||
settings.link.button=Atualizar Link do Repositório
|
||||
settings.link.select=Selecionar repositório
|
||||
settings.link.button=Atualizar link do repositório
|
||||
settings.link.success=Link do repositório foi atualizado com sucesso.
|
||||
settings.link.error=Falha ao atualizar o link do repositório.
|
||||
settings.delete=Excluir o pacote
|
||||
|
@ -4004,7 +4004,7 @@ variables.not_found = Não foi possível encontrar a variável.
|
|||
type-1.display_name=Projeto individual
|
||||
type-2.display_name=Projeto do repositório
|
||||
type-3.display_name=Projeto da organização
|
||||
deleted.display_name = Projeto Apagado
|
||||
deleted.display_name = Projeto apagado
|
||||
|
||||
[git.filemode]
|
||||
symbolic_link=Ligação simbólica
|
||||
|
|
|
@ -9,7 +9,7 @@ sign_in_with_provider=Iniciar sessão com %s
|
|||
sign_in_or=ou
|
||||
sign_out=Terminar sessão
|
||||
sign_up=Fazer inscrição
|
||||
link_account=Vincular conta
|
||||
link_account=Associar conta
|
||||
register=Inscrição
|
||||
version=Versão
|
||||
powered_by=Implementado com %s
|
||||
|
@ -20,12 +20,12 @@ notifications=Notificações
|
|||
active_stopwatch=Cronómetro em andamento
|
||||
tracked_time_summary=Resumo do tempo rastreado com base em filtros da lista de questões
|
||||
create_new=Criar…
|
||||
user_profile_and_more=Perfil e configurações…
|
||||
user_profile_and_more=Perfil e definições…
|
||||
signed_in_as=Sessão iniciada como
|
||||
enable_javascript=Este sítio Web requer JavaScript.
|
||||
toc=Índice
|
||||
licenses=Licenças
|
||||
return_to_forgejo=Retornar ao Forgejo
|
||||
return_to_forgejo=Voltar ao Forgejo
|
||||
|
||||
username=Nome de utilizador
|
||||
email=Endereço de email
|
||||
|
@ -59,10 +59,10 @@ new_migrate=Nova migração
|
|||
new_mirror=Nova réplica
|
||||
new_fork=Nova derivação do repositório
|
||||
new_org=Nova organização
|
||||
new_project=Novo planeamento
|
||||
new_project=Novo projeto
|
||||
new_project_column=Nova coluna
|
||||
manage_org=Gerir organizações
|
||||
admin_panel=Administração do sítio
|
||||
admin_panel=Administração do site
|
||||
account_settings=Configurações da conta
|
||||
settings=Configurações
|
||||
your_profile=Perfil
|
||||
|
@ -155,7 +155,7 @@ invalid_data = Dados inválidos: %v
|
|||
filter.clear = Retirar filtros
|
||||
filter.is_archived = Arquivado
|
||||
filter.not_template = Não modelos
|
||||
toggle_menu = Comutar menu
|
||||
toggle_menu = Alternar menu
|
||||
filter = Filtrar
|
||||
copy_generic = Copiar para a área de transferência
|
||||
test = Teste
|
||||
|
@ -233,7 +233,7 @@ platform_desc=Está confirmado que Forgejo corre em sistemas operativos livres,
|
|||
lightweight=Leve
|
||||
lightweight_desc=Forgejo requer poucos recursos e pode correr num simples Raspberry Pi. Economize a energia da sua máquina!
|
||||
license=Código aberto
|
||||
license_desc=Vá buscá-lo em <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Junte-se a nós dando a <a target="_blank" rel="noopener noreferrer" href="%[2]s">sua contribuição</a> para tornar este programa ainda melhor. Não se acanhe e contribua!
|
||||
license_desc=Todas as fontes estão disponíveis no <a target="_blank" rel="noopener noreferrer" href="%[1]s">Forgejo</a>! Junte-se a nós e <a target="_blank" rel="noopener noreferrer" href="%[2]s">contribua</a> para tornar este projeto ainda melhor. Não receie tornar-se colaborador!
|
||||
|
||||
[install]
|
||||
install=Instalação
|
||||
|
@ -291,7 +291,7 @@ smtp_port=Porto do SMTP
|
|||
smtp_from=Email do remetente
|
||||
smtp_from_helper=Endereço de email que o Forgejo vai usar. Insira um endereço de email simples ou use o formato "Nome" <email@exemplo.com>.
|
||||
mailer_user=Nome de utilizador do SMTP
|
||||
mailer_password=Senha do SMTP
|
||||
mailer_password=Palavra-passe do SMTP
|
||||
register_confirm=Exigir confirmação de email para se inscrever
|
||||
mail_notify=Habilitar notificações por email
|
||||
server_service_title=Configurações do servidor e de terceiros
|
||||
|
@ -316,7 +316,7 @@ admin_setting.description=A criação de uma conta de administração é opciona
|
|||
admin_title=Configurações da conta de administração
|
||||
admin_name=Nome de utilizador do administrador
|
||||
admin_password=Senha
|
||||
confirm_password=Confirme a senha
|
||||
confirm_password=Confirme a palavra-passe
|
||||
admin_email=Endereço de email
|
||||
install_btn_confirm=Instalar Forgejo
|
||||
test_git_failed=Não foi possível testar o comando "git": %v
|
||||
|
@ -339,7 +339,7 @@ default_enable_timetracking=Habilitar, por norma, a contagem do tempo
|
|||
default_enable_timetracking.description=Habilitar, por norma, a contagem do tempo nos novos repositórios.
|
||||
no_reply_address=Domínio dos emails ocultos
|
||||
no_reply_address_helper=Nome de domínio para utilizadores com um endereço de email oculto. Por exemplo, o nome de utilizador "silva" será registado no Git como "silva@semresposta.exemplo.org" se o domínio de email oculto estiver definido como "semresposta.exemplo.org".
|
||||
password_algorithm=Algoritmo de Hash da Senha
|
||||
password_algorithm=Algoritmo de Hash da palavra-passe
|
||||
invalid_password_algorithm=Algoritmo de hash da senha inválido
|
||||
password_algorithm_helper=Definir o algoritmo de hash da senha. Os algoritmos têm requisitos e resistência distintos. `argon2` é bastante seguro, mas usa muita memória e pode ser inapropriado para sistemas pequenos.
|
||||
enable_update_checker=Habilitar verificador de novidades
|
||||
|
@ -415,7 +415,7 @@ disable_register_mail=A confirmação por email da inscrição está desabilitad
|
|||
manual_activation_only=Contacte o administrador para completar a habilitação.
|
||||
remember_me=Memorizar este dispositivo
|
||||
remember_me.compromised=O identificador da sessão já não é válido, o que pode indicar uma conta comprometida. Verifique se a sua conta apresenta operações pouco habituais.
|
||||
forgot_password_title=Esqueci-me da senha
|
||||
forgot_password_title=Esqueci-me da palavra-passe
|
||||
forgot_password=Esqueceu a sua senha?
|
||||
sign_up_now=Precisa de uma conta? Inscreva-se agora.
|
||||
sign_up_successful=A conta foi criada com sucesso. Bem-vindo/a!
|
||||
|
@ -744,7 +744,7 @@ social=Contas sociais
|
|||
applications=Aplicações
|
||||
orgs=Organizações
|
||||
repos=Repositórios
|
||||
delete=Eliminar a conta
|
||||
delete=Eliminar conta
|
||||
twofa=Autenticação em dois passos (TOTP)
|
||||
account_link=Contas vinculadas
|
||||
organization=Organizações
|
||||
|
@ -1060,7 +1060,7 @@ user_unblock_success = O utilizador foi desbloqueado com sucesso.
|
|||
language.title = Idioma predefinido
|
||||
keep_activity_private.description = O seu <a href="%s">trabalho público</a> apenas estará visível para si e para os administradores da instância.
|
||||
language.description = Este idioma vai ser guardado na sua conta e ser usado como o predefinido depois de iniciar sessão.
|
||||
language.localization_project = Ajude-nos a traduzir o Forgejo para o seu idioma! <a href="%s">Saiba mais</a>.
|
||||
language.localization_project = Ajude-nos a traduzir o Forgejo para o seu idioma! <a href="%s">Ler mais</a>.
|
||||
pronouns_custom_label = Pronomes personalizados
|
||||
user_block_yourself = Não se pode bloquear a si próprio.
|
||||
change_username_redirect_prompt.with_cooldown.one = O nome de utilizador antigo estará disponível para todos após um período de espera de %[1]d dia, podendo ainda reivindicar o nome de utilizador antigo durante o período de espera.
|
||||
|
@ -1093,7 +1093,7 @@ regenerate_token = Regenerar
|
|||
access_token_regeneration_desc = A regeneração de um código irá revogar o acesso à sua conta para as aplicações que o utilizam. Isto não pode ser anulado. Continuar?
|
||||
|
||||
[repo]
|
||||
new_repo_helper=Um repositório contém todos os ficheiros do trabalho, incluindo o histórico das revisões. Já tem um hospedado noutro sítio? <a href="%s">Migre o repositório</a>.
|
||||
new_repo_helper=Um repositório contém todos os ficheiros do projeto, incluindo o histórico das revisões. Já tem um hospedado noutro sítio? <a href="%s">Migre o repositório</a>.
|
||||
owner=Proprietário(a)
|
||||
owner_helper=Algumas organizações podem não aparecer na lista suspensa devido a um limite máximo de contagem de repositórios.
|
||||
repo_name=Nome do repositório
|
||||
|
@ -1133,7 +1133,7 @@ issue_labels=Rótulos
|
|||
issue_labels_helper=Escolha um conjunto de rótulos
|
||||
license=Licença
|
||||
license_helper=Escolha um ficheiro de licença
|
||||
license_helper_desc=Uma licença rege o que os outros podem, ou não, fazer com o seu código fonte. Não tem a certeza sobre qual a mais indicada para o seu trabalho? Veja: <a target="_blank" rel="noopener noreferrer" href="%s">Escolher uma licença</a>.
|
||||
license_helper_desc=Uma licença rege o que os outros podem, ou não, fazer com o seu código fonte. Não tem a certeza sobre qual a mais indicada para o seu projeto? Veja: <a target="_blank" rel="noopener noreferrer" href="%s">Escolher uma licença</a>.
|
||||
object_format=Formato dos elementos
|
||||
object_format_helper=Formato dos elementos do repositório. Não poderá ser alterado mais tarde. SHA1 é o mais compatível.
|
||||
readme=README
|
||||
|
@ -1479,23 +1479,23 @@ projects=Planeamentos
|
|||
projects.desc=Gerir questões e integrações nos quadros do planeamento.
|
||||
projects.description=Descrição (opcional)
|
||||
projects.description_placeholder=Descrição
|
||||
projects.create=Criar planeamento
|
||||
projects.create=Criar projeto
|
||||
projects.title=Título
|
||||
projects.new=Novo planeamento
|
||||
projects.new=Novo projeto
|
||||
projects.new_subheader=Coordene, acompanhe e modifique o seu trabalho num só lugar, para que os planeamentos se mantenham transparentes e cumpram o calendário.
|
||||
projects.create_success=O planeamento "%s" foi criado.
|
||||
projects.deletion=Eliminar planeamento
|
||||
projects.deletion=Eliminar projeto
|
||||
projects.deletion_desc=Eliminar um planeamento remove-o de todas as questões relacionadas. Continuar?
|
||||
projects.deletion_success=O planeamento foi eliminado.
|
||||
projects.edit=Editar planeamentos
|
||||
projects.edit=Editar projeto
|
||||
projects.edit_subheader=Planeamentos organizam questões e acompanham o progresso.
|
||||
projects.modify=Editar planeamento
|
||||
projects.modify=Editar projeto
|
||||
projects.edit_success=O planeamento "%s" foi modificado.
|
||||
projects.type.none=Nenhum
|
||||
projects.type.basic_kanban=Kanban básico
|
||||
projects.type.bug_triage=Triagem de erros
|
||||
projects.template.desc=Modelo
|
||||
projects.template.desc_helper=Escolha um modelo de planeamento para começar
|
||||
projects.template.desc_helper=Escolha um modelo de projeto para começar
|
||||
projects.type.uncategorized=Sem categoria
|
||||
projects.column.edit=Editar coluna
|
||||
projects.column.edit_title=Nome
|
||||
|
@ -1507,7 +1507,7 @@ projects.column.set_default_desc=Definir esta coluna como a predefinida para que
|
|||
projects.column.unset_default=Deixar de ser a predefinida
|
||||
projects.column.unset_default_desc=Faz com que esta coluna deixe de ser a predefinida
|
||||
projects.column.delete=Eliminar coluna
|
||||
projects.column.deletion_desc=Eliminar uma coluna de um planeamento faz com que todas as questões que nela constam sejam movidas para a coluna padrão. Continuar?
|
||||
projects.column.deletion_desc=Eliminar uma coluna de um projeto faz com que todas as questões que nela constam sejam movidas para a coluna padrão. Continuar?
|
||||
projects.column.color=Colorido
|
||||
projects.open=Abrir
|
||||
projects.close=Fechar
|
||||
|
@ -1519,7 +1519,7 @@ projects.card_type.text_only=Apenas texto
|
|||
issues.desc=Organize relatórios de erros, tarefas e etapas.
|
||||
issues.filter_assignees=Filtrar encarregado
|
||||
issues.filter_milestones=Filtrar etapa
|
||||
issues.filter_projects=Filtrar planeamento
|
||||
issues.filter_projects=Filtrar projeto
|
||||
issues.filter_labels=Filtrar rótulo
|
||||
issues.filter_reviewers=Filtrar revisor
|
||||
issues.new=Questão nova
|
||||
|
@ -1530,8 +1530,8 @@ issues.new.clear_labels=Retirar rótulos
|
|||
issues.new.projects=Planeamentos
|
||||
issues.new.clear_projects=Limpar planeamentos
|
||||
issues.new.no_projects=Nenhum planeamento
|
||||
issues.new.open_projects=Planeamentos abertos
|
||||
issues.new.closed_projects=Planeamentos fechados
|
||||
issues.new.open_projects=Projetos abertos
|
||||
issues.new.closed_projects=Projetos fechados
|
||||
issues.new.no_items=Sem itens
|
||||
issues.new.milestone=Etapa
|
||||
issues.new.no_milestone=Sem etapa
|
||||
|
@ -2049,7 +2049,7 @@ ext_wiki=Wiki externo
|
|||
ext_wiki.desc=Ligação para um wiki externo.
|
||||
|
||||
wiki=Wiki
|
||||
wiki.welcome=Bem-vindo(a) ao Wiki.
|
||||
wiki.welcome=Bem-vindo(a) à wiki.
|
||||
wiki.welcome_desc=O wiki permite escrever e partilhar documentação com os colaboradores.
|
||||
wiki.desc=Escrever e partilhar documentação com os colaboradores.
|
||||
wiki.create_first_page=Criar a primeira página
|
||||
|
@ -2236,7 +2236,7 @@ settings.pulls.default_delete_branch_after_merge=Eliminar, por norma, o ramo do
|
|||
settings.pulls.default_allow_edits_from_maintainers=Permitir, por norma, que os responsáveis editem
|
||||
settings.releases_desc=Habilitar lançamentos no repositório
|
||||
settings.packages_desc=Habilitar o registo de pacotes do repositório
|
||||
settings.projects_desc=Habilitar planeamentos no repositório
|
||||
settings.projects_desc=Habilitar projetos no repositório
|
||||
settings.actions_desc=Habilitar sequências CI/CD integradas com Forgejo Actions
|
||||
settings.admin_settings=Configurações do administrador
|
||||
settings.admin_enable_health_check=Habilitar verificações de integridade (git fsck) no repositório
|
||||
|
@ -2834,7 +2834,7 @@ form.string_too_long = O texto fornecido é mais comprido do que %d caracteres.
|
|||
settings.federation_settings = Configurações da federação
|
||||
settings.federation_apapiurl = URL de federação deste repositório. Copie e cole nas configurações de federação de outro repositório como um URL de um repositório que está a ser seguido.
|
||||
issues.edit.already_changed = Não foi possível guardar as modificações desta questão. O conteúdo parece ter sido modificado por outro utilizador. Refresque a página e tente editar novamente para evitar sobrescrever as modificações que fizeram
|
||||
project = Planeamentos
|
||||
project = Projetos
|
||||
pulls.edit.already_changed = Não foi possível guardar as modificações do pedido de integração. O conteúdo parece ter sido modificado por outro utilizador. Refresque a página e tente editar novamente para evitar sobrescrever as modificações que fizeram
|
||||
subscribe.issue.guest.tooltip = Inicie sessão para subscrever esta questão.
|
||||
subscribe.pull.guest.tooltip = Inicie sessão para subscrever este pedido de integração.
|
||||
|
@ -3811,7 +3811,7 @@ swift.install2=e execute o seguinte comando:
|
|||
vagrant.install=Para adicionar uma máquina virtual Vagrant, execute o seguinte comando:
|
||||
settings.link=Vincular este pacote a um repositório
|
||||
settings.link.description=Se você vincular um pacote a um repositório, o pacote será listado na lista de pacotes do repositório.
|
||||
settings.link.select=Escolha o repositório
|
||||
settings.link.select=Escolher repositório
|
||||
settings.link.button=Modificar vínculo ao repositório
|
||||
settings.link.success=O vínculo ao repositório foi modificado com sucesso.
|
||||
settings.link.error=Falhou a modificação do vínculo ao repositório.
|
||||
|
@ -4002,10 +4002,10 @@ runs.no_workflows.help_write_access = Não sabe como começar com o Forgejo Acti
|
|||
variables.not_found = Não foi possível encontrar a variável.
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Planeamento individual
|
||||
type-2.display_name=Planeamento do repositório
|
||||
type-3.display_name=Planeamento da organização
|
||||
deleted.display_name = Planeamento eliminado
|
||||
type-1.display_name=Projeto individual
|
||||
type-2.display_name=Projeto do repositório
|
||||
type-3.display_name=Projeto da organização
|
||||
deleted.display_name = Projeto eliminado
|
||||
|
||||
[git.filemode]
|
||||
changed_filemode=%[1]s → %[2]s
|
||||
|
@ -4024,7 +4024,7 @@ code_search_by_git_grep = Os resultados da pesquisa no código-fonte neste momen
|
|||
no_results = Não foram encontrados resultados correspondentes.
|
||||
package_kind = Pesquisar pacotes…
|
||||
runner_kind = Pesquisar executores…
|
||||
project_kind = Pesquisar planeamentos…
|
||||
project_kind = Pesquisar projetos…
|
||||
branch_kind = Pesquisar ramos…
|
||||
commit_kind = Pesquisar cometimentos…
|
||||
search = Procurar…
|
||||
|
@ -4068,8 +4068,8 @@ test = ok :)
|
|||
[repo.permissions]
|
||||
code.read = <b>Ler:</b> Aceder e clonar o código-fonte do repositório.
|
||||
releases.read = <b>Ler:</b> Ver e descarregar lançamentos.
|
||||
projects.read = <b>Ler:</b> Aceder aos quadros de planeamento do repositório.
|
||||
projects.write = <b>Escrever:</b> Criar planeamentos e colunas e editá-las.
|
||||
projects.read = <b>Ler:</b> Aceder aos quadros de projeto do repositório.
|
||||
projects.write = <b>Escrever:</b> Criar projetos e colunas e editá-las.
|
||||
packages.read = <b>Ler:</b> Ver e descarregar pacotes atribuídos ao repositório.
|
||||
packages.write = <b>Escrever:</b> Publicar e eliminar pacotes atribuídos ao repositório.
|
||||
actions.read = <b>Ler:</b> Ver sequências CI/CD integrados e os seus registos.
|
||||
|
|
|
@ -296,7 +296,7 @@ federated_avatar_lookup.description=Libravatar kullanarak federe profil resmi ar
|
|||
disable_registration=Kendi Kendine Kaydolmayı Devre Dışı Bırak
|
||||
disable_registration.description=Kullanıcının kendi kendine kaydolmasını devre dışı bırak. Yalnızca yöneticiler yeni hesaplar oluşturabilecek.
|
||||
allow_only_external_registration.description=Sadece belirlenen dış hizmetler aracılığıyla kullanıcı kaydına izin ver.
|
||||
openid_signin=OpenID Oturum Açmayı Etkinleştiriniz
|
||||
openid_signin=OpenID Oturum Açmayı Etkinleştir
|
||||
openid_signin.description=OpenID ile kullanıcı girişini etkinleştir.
|
||||
openid_signup=OpenID ile Kendi Kendine Kaydı Etkinleştir
|
||||
openid_signup.description=OpenID Tabanlı Kendi Kendi Kullanıcı Kaydını Etkinleştir.
|
||||
|
@ -3061,13 +3061,13 @@ packages.repository=Depo
|
|||
packages.size=Boyut
|
||||
packages.published=Yayınlandı
|
||||
|
||||
defaulthooks=Varsayılan Web İstemcileri
|
||||
defaulthooks.desc=Web İstemcileri, belirli Forgejo olayları tetiklendiğinde otomatik olarak HTTP POST isteklerini sunucuya yapar. Burada tanımlanan Web İstemcileri varsayılandır ve tüm yeni depolara kopyalanır. <a target="_blank" rel="noopener" href="%s">web istemcileri kılavuzunda</a> daha fazla bilgi edinin.
|
||||
defaulthooks=Varsayılan web kancaları
|
||||
defaulthooks.desc=Web Kancaları, belirli Forgejo olayları tetiklendiğinde otomatik olarak HTTP POST isteklerini sunucuya yapar. Burada tanımlanan Web kancaları varsayılandır ve tüm yeni depolara kopyalanır. <a target="_blank" rel="noopener" href="%s">web kancaları kılavuzunda</a> daha fazla bilgi edinin.
|
||||
defaulthooks.add_webhook=Varsayılan Web İstemcisi Ekle
|
||||
defaulthooks.update_webhook=Varsayılan Web İstemcisini Güncelle
|
||||
|
||||
systemhooks=Sistem Web İstemcileri
|
||||
systemhooks.desc=Belirli Forgejo olayları tetiklendiğinde Web istemcileri otomatik olarak bir sunucuya HTTP POST istekleri yapar. Burada tanımlanan web istemcileri sistemdeki tüm depolar üzerinde çalışır, bu yüzden lütfen bunun olabilecek tüm performans sonuçlarını göz önünde bulundurun. <a target="_blank" rel="noopener" href="%s">web istemcileri kılavuzunda</a> daha fazla bilgi edinin.
|
||||
systemhooks=Sistem web kancaları
|
||||
systemhooks.desc=Belirli Forgejo olayları tetiklendiğinde Web kancaları otomatik olarak bir sunucuya HTTP POST istekleri yapar. Burada tanımlanan web kancaları sistemdeki tüm depolar üzerinde çalışır, bu yüzden lütfen bunun olabilecek tüm performans sonuçlarını göz önünde bulundurun. <a target="_blank" rel="noopener" href="%s">web kancaları kılavuzunda</a> daha fazla bilgi edinin.
|
||||
systemhooks.add_webhook=Sistem Web İstemcisi Ekle
|
||||
systemhooks.update_webhook=Sistem Web İstemcisi Güncelle
|
||||
|
||||
|
@ -3241,7 +3241,7 @@ config.disable_register=Kullanıcı Kaydını Devre Dışı Bırak
|
|||
config.allow_only_internal_registration=Kayda Sadece Forgejo'nın Kendisi Üzerinden İzin Ver
|
||||
config.allow_only_external_registration=Sadece Dış Hizmetler Aracılığıyla Kullanıcı Kaydına İzin Ver
|
||||
config.enable_openid_signup=OpenID Kendinden Kaydı'nı Etkinleştir
|
||||
config.enable_openid_signin=OpenID Oturum Açmayı Etkinleştiriniz
|
||||
config.enable_openid_signin=OpenID Oturum Açmayı Etkinleştir
|
||||
config.show_registration_button=Kaydolma Düğmesini Göster
|
||||
config.require_sign_in_view=Sayfaları Görüntülemek için Giriş Yapmaya Zorla
|
||||
config.mail_notify=E-Posta Bildirimlerini Etkinleştir
|
||||
|
|
|
@ -1780,7 +1780,7 @@ milestones.filter_sort.least_issues=Найменш задач
|
|||
ext_wiki.desc=Посилання на зовнішню вікі.
|
||||
|
||||
wiki=Вікі
|
||||
wiki.welcome=Ласкаво просимо до Вікі.
|
||||
wiki.welcome=Ласкаво просимо до вікі.
|
||||
wiki.welcome_desc=Wiki дозволяє писати та ділитися документацією з співавторами.
|
||||
wiki.desc=Пишіть та обмінюйтеся документацією із співавторами.
|
||||
wiki.create_first_page=Створити першу сторінку
|
||||
|
@ -2663,6 +2663,11 @@ sync_fork.button = Синхронізувати
|
|||
sync_fork.branch_behind_one = Ця гілка на %[1]d коміт позаду %[2]s
|
||||
sync_fork.branch_behind_few = Ця гілка на %[1]d комітів позаду %[2]s
|
||||
issues.role.first_time_contributor = Новий учасник
|
||||
settings.event_action_failure = Помилка
|
||||
settings.event_action_success = Успіх
|
||||
settings.event_action_recover = Відновлено
|
||||
commitstatus.success = Успіх
|
||||
commitstatus.failure = Збій
|
||||
|
||||
[graphs]
|
||||
contributors.what = внески
|
||||
|
|
|
@ -7,9 +7,9 @@ logo=徽标
|
|||
sign_in=登录
|
||||
sign_in_with_provider=使用 %s 登录
|
||||
sign_in_or=或
|
||||
sign_out=退出
|
||||
sign_out=登出
|
||||
sign_up=注册
|
||||
link_account=链接账户
|
||||
link_account=链接账号
|
||||
register=注册
|
||||
version=当前版本
|
||||
powered_by=由 %s 提供支持
|
||||
|
@ -743,7 +743,7 @@ social=社交帐号
|
|||
applications=应用
|
||||
orgs=组织
|
||||
repos=仓库列表
|
||||
delete=删除帐户
|
||||
delete=删除账号
|
||||
twofa=两步验证(TOTP)
|
||||
account_link=已绑定的帐户
|
||||
organization=组织
|
||||
|
@ -1515,7 +1515,7 @@ projects.card_type.images_and_text=图标和文字
|
|||
projects.card_type.text_only=仅文本
|
||||
|
||||
issues.desc=组织 bug 报告、任务和里程碑。
|
||||
issues.filter_assignees=筛选指派人
|
||||
issues.filter_assignees=筛选指派成员
|
||||
issues.filter_milestones=筛选里程碑
|
||||
issues.filter_projects=筛选项目
|
||||
issues.filter_labels=筛选标签
|
||||
|
@ -1653,7 +1653,7 @@ issues.comment_pull_merged_at=已合并提交 %[1]s 到 %[2]s %[3]s
|
|||
issues.comment_manually_pull_merged_at=手动合并提交 %[1]s 到 %[2]s %[3]s
|
||||
issues.close_comment_issue=评论并关闭
|
||||
issues.reopen_issue=重新开放
|
||||
issues.reopen_comment_issue=重新打开评论
|
||||
issues.reopen_comment_issue=重新打开并评论
|
||||
issues.create_comment=评论
|
||||
issues.closed_at=`于<a id="%[1]s" href="#%[1]s">%[2]s</a>关闭此议题`
|
||||
issues.reopened_at=`重新打开此问题 <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
|
@ -2304,7 +2304,7 @@ settings.add_collaborator_inactive_user=无法添加未激活的用户作为合
|
|||
settings.add_collaborator_owner=不能将所有者添加为协作者。
|
||||
settings.add_collaborator_duplicate=合作者已经被添加到本仓库。
|
||||
settings.delete_collaborator=删除
|
||||
settings.collaborator_deletion=删除协作者
|
||||
settings.collaborator_deletion=移除协作者
|
||||
settings.collaborator_deletion_desc=删除协作者后他将无法再对此仓库的访问。继续?
|
||||
settings.remove_collaborator_success=已成功删除协作者。
|
||||
settings.search_user_placeholder=搜索用户...
|
||||
|
|
|
@ -2570,8 +2570,8 @@ tree_path_not_found_tag = 路徑 %[1]s 不存在於標籤 %[2]s 中
|
|||
tree_path_not_found_commit = 路徑 %[1]s 不存在於提交 %[2]s 中
|
||||
tree_path_not_found_branch = 路徑 %[1]s 不存在於分支 %[2]s 中
|
||||
transfer.no_permission_to_accept = 您沒有權限接受這項轉讓。
|
||||
archive.title = 這個儲存庫被封存了。您可以檢視其中的檔案或是 Clone 它,但您無法推送提交,提出問題或合併請求。
|
||||
archive.title_date = 這個儲存庫在 %s 被封存了。您可以檢視其中的檔案或 clone 它,但您無法推送提交,提出問題或合併請求。
|
||||
archive.title = 這個儲存庫已被封存。您可以檢視其中的檔案或拓製儲存庫,但您無法提交推送和創建新議題、合併請求或評論。
|
||||
archive.title_date = 這個儲存庫在 %s 被封存了。您可以檢視其中的檔案或拓製儲存庫,但您無法提交推送和創建新議題、合併請求或評論。
|
||||
migrate.forgejo.description = 從 codeberg.org 或其他 Forgejo 站點遷移資料。
|
||||
migrate.cancel_migrating_title = 取消遷移
|
||||
executable_file = 可執行檔
|
||||
|
|
|
@ -98,5 +98,7 @@
|
|||
"followers.incoming.list.none": "Tohoto uživatele nikdo nesleduje.",
|
||||
"followers.outgoing.list.none": "%s nikoho nesleduje.",
|
||||
"stars.list.none": "Tento repozitář si nikdo nepřidal do oblíbených.",
|
||||
"followers.outgoing.list.self.none": "Nikoho nesledujete."
|
||||
"followers.outgoing.list.self.none": "Nikoho nesledujete.",
|
||||
"editor.textarea.tab_hint": "Řádek je již odsazen. Pro opuštění editoru stiskněte znovu <kbd>Tab</kbd> nebo <kbd>Escape</kbd>.",
|
||||
"editor.textarea.shift_tab_hint": "Na tomto řádku není žádné odsazení. Pro opuštění editoru stiskněte znovu <kbd>Shift</kbd> + <kbd>Tab</kbd> nebo <kbd>Escape</kbd>."
|
||||
}
|
||||
|
|
|
@ -84,5 +84,13 @@
|
|||
"moderation.report_remarks.placeholder": "Angiv venligst nogle detaljer vedrørende det misbrug, du anmelder.",
|
||||
"moderation.submit_report": "Indsend rapport",
|
||||
"moderation.reporting_failed": "Den nye misbrugsrapport kunne ikke indsendes: %v",
|
||||
"moderation.reported_thank_you": "Tak for din rapport. Administrationen er blevet gjort opmærksom på den."
|
||||
"moderation.reported_thank_you": "Tak for din rapport. Administrationen er blevet gjort opmærksom på den.",
|
||||
"stars.list.none": "Ingen har markeret dette depot med en stjerne.",
|
||||
"watch.list.none": "Ingen ser dette depot.",
|
||||
"followers.incoming.list.self.none": "Ingen følger din profil.",
|
||||
"followers.incoming.list.none": "Ingen følger denne bruger.",
|
||||
"followers.outgoing.list.self.none": "Du følger ikke nogen.",
|
||||
"followers.outgoing.list.none": "%s følger ikke nogen.",
|
||||
"editor.textarea.tab_hint": "Linjen er allerede indrykket. Tryk på <kbd>Tab</kbd> igen eller <kbd>Escape</kbd> for at forlade editoren.",
|
||||
"editor.textarea.shift_tab_hint": "Ingen indrykning på denne linje. Tryk på <kbd>Shift</kbd> + <kbd>Tab</kbd> igen eller <kbd>Escape</kbd> for at forlade editoren."
|
||||
}
|
||||
|
|
|
@ -90,5 +90,7 @@
|
|||
"followers.outgoing.list.self.none": "Du folgst niemanden.",
|
||||
"followers.outgoing.list.none": "%s folgt niemanden.",
|
||||
"stars.list.none": "Niemand hat dieses Repo favorisiert.",
|
||||
"followers.incoming.list.none": "Niemand folgt diesem Benutzer."
|
||||
"followers.incoming.list.none": "Niemand folgt diesem Benutzer.",
|
||||
"editor.textarea.tab_hint": "Zeile bereits eingerückt. Drücke nochmals <kbd>Tab</kbd> oder <kbd>Escape</kbd> um den Editor zu verlassen.",
|
||||
"editor.textarea.shift_tab_hint": "Keine Einrückung auf dieser Zeile. Drücke nochmals <kbd>Shift</kbd> + <kbd>Tab</kbd> oder <kbd>Escape</kbd> um den Editor zu verlassen."
|
||||
}
|
||||
|
|
|
@ -92,5 +92,6 @@
|
|||
"discussion.locked": "This discussion has been locked. Commenting is limited to contributors.",
|
||||
"editor.textarea.tab_hint": "Line already indented. Press <kbd>Tab</kbd> again or <kbd>Escape</kbd> to leave the editor.",
|
||||
"editor.textarea.shift_tab_hint": "No indentation on this line. Press <kbd>Shift</kbd> + <kbd>Tab</kbd> again or <kbd>Escape</kbd> to leave the editor.",
|
||||
"admin.dashboard.cleanup_offline_runners": "Cleanup offline runners",
|
||||
"meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it."
|
||||
}
|
||||
|
|
|
@ -83,5 +83,14 @@
|
|||
"moderation.submit_report": "I-submit ang ulat",
|
||||
"moderation.reporting_failed": "Hindi ma-submit ang bagong ulat sa pang aabuso: %v",
|
||||
"moderation.reported_thank_you": "Salamat sa iyong ulat. Naipaalam na ito sa administrasyon.",
|
||||
"repo.form.cannot_create": "Naabot na ng lahat ng mga espasyo kung saan ka makakagawa ng mga repositoryo ang limitasyon ng mga repositoryo."
|
||||
"repo.form.cannot_create": "Naabot na ng lahat ng mga espasyo kung saan ka makakagawa ng mga repositoryo ang limitasyon ng mga repositoryo.",
|
||||
"stars.list.none": "Wala pang nag-star ng repositoryong ito.",
|
||||
"followers.incoming.list.self.none": "Walang sumusubaybay sa iyong profile.",
|
||||
"repo.issue_indexer.title": "Indexer ng Isyu",
|
||||
"watch.list.none": "Wala pang nanonood sa repositoryong ito.",
|
||||
"followers.incoming.list.none": "Wala pang sumusunod sa user na ito.",
|
||||
"followers.outgoing.list.self.none": "Hindi ka sumusunod ng anumang tao.",
|
||||
"followers.outgoing.list.none": "Hindi sinusundan ni %s ang sinuman.",
|
||||
"editor.textarea.tab_hint": "Naka-indent na ang linya. Pindutin ulit ang <kbd>Tab</kbd> o <kbd>Escape</kbd> para umalis sa editor.",
|
||||
"editor.textarea.shift_tab_hint": "Walang indentation sa linyang ito. Pindutin ang <kbd>Shift</kbd> + <kbd>Tab</kbd> ulit o <kbd>Escape</kbd> para umalis sa editor."
|
||||
}
|
||||
|
|
|
@ -89,5 +89,13 @@
|
|||
"moderation.report_remarks": "Remarques",
|
||||
"moderation.report_remarks.placeholder": "S'il vous plaît fournissez quelques détails en rapport avec l'abus que vous signalez.",
|
||||
"moderation.submit_report": "Soumettre le signalement",
|
||||
"moderation.reporting_failed": "Impossible de soumettre le nouveau signalement : %v"
|
||||
"moderation.reporting_failed": "Impossible de soumettre le nouveau signalement : %v",
|
||||
"followers.incoming.list.self.none": "Personne ne suit votre profile.",
|
||||
"followers.incoming.list.none": "Personne ne suit cet utilisateur.",
|
||||
"followers.outgoing.list.self.none": "Vous ne suivez personne.",
|
||||
"followers.outgoing.list.none": "%s ne vous suit plus.",
|
||||
"repo.issue_indexer.title": "Indexeur de problèmes",
|
||||
"stars.list.none": "Personne n'a mis d'étoiles sur ce dépôt.",
|
||||
"watch.list.none": "Personne ne consulte ce dépôt.",
|
||||
"repo.form.cannot_create": "Tous les espaces dans lesquels vous pouvez créer des dépôts ont atteint la limite de dépôts."
|
||||
}
|
||||
|
|
|
@ -98,5 +98,7 @@
|
|||
"followers.outgoing.list.self.none": "Tu nevienam neseko.",
|
||||
"followers.outgoing.list.none": "%s nevienam neseko.",
|
||||
"stars.list.none": "Neviens šo glabātavu nav atzīmējis ar zvaigzni.",
|
||||
"followers.incoming.list.self.none": "Neviens neseko Tavam profilam."
|
||||
"followers.incoming.list.self.none": "Neviens neseko Tavam profilam.",
|
||||
"editor.textarea.tab_hint": "Rinda jau ir ar atkāpi. Spied <kbd>Tab</kbd> vēlreiz vai <kbd>Escape</kbd>, lai izietu no redaktora!",
|
||||
"editor.textarea.shift_tab_hint": "Šajā rindā nav atkāpes. Spied <kbd>Shift</kbd> + <kbd>Tab</kbd> vēlreiz vai <kbd>Escape</kbd>, lai izietu no redaktora!"
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"repo.pulls.merged_title_desc": {
|
||||
"one": "heeft %[1]d commit van <code>%[2]s</code> samengevoegd in <code>%[3]s</code> %[4]s",
|
||||
"other": "heeft %[1]d commits samengevoegd van <code>%[2]s</code> naar <code>%[3]s</code> %[4]s"
|
||||
"other": "heeft %[1]d commits van <code>%[2]s</code> samengevoegd in <code>%[3]s</code> %[4]s"
|
||||
},
|
||||
"repo.pulls.title_desc": {
|
||||
"one": "wil %[1]d commit van <code>%[2]s</code> samenvoegen in <code id=\"%[4]s\">%[3]s</code>",
|
||||
"other": "wil %[1]d commits van <code>%[2]s</code> samenvoegen met <code id=\"%[4]s\">%[3]s</code>"
|
||||
"other": "wil %[1]d commits van <code>%[2]s</code> samenvoegen in <code id=\"%[4]s\">%[3]s</code>"
|
||||
},
|
||||
"search.milestone_kind": "Zoek mijlpalen…",
|
||||
"home.welcome.no_activity": "Geen activiteit",
|
||||
|
@ -13,27 +13,27 @@
|
|||
"home.explore_repos": "Verken repositories",
|
||||
"home.explore_users": "Verken gebruikers",
|
||||
"home.explore_orgs": "Verken organisaties",
|
||||
"incorrect_root_url": "Deze Forgejo-instantie is geconfigureerd om geserveerd te worden op \"%s\". U bekijkt Forgejo momenteel via een andere URL, waardoor onderdelen van de applicatie kunnen breken. De canonieke URL kan worden gewijzigd door Forgejo admins via de ROOT_URL instelling in de app.ini.",
|
||||
"incorrect_root_url": "Deze Forgejo-instantie is geconfigureerd om bereikbaar te zijn op \"%s\". U bekijkt Forgejo momenteel via een andere URL, waardoor onderdelen van de applicatie kunnen breken. De canonieke URL kan worden gewijzigd door Forgejo admins via de ROOT_URL instelling in de app.ini.",
|
||||
"themes.names.forgejo-auto": "Forgejo (volg het systeemthema)",
|
||||
"themes.names.forgejo-light": "Forgejo licht",
|
||||
"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.",
|
||||
"install.invalid_lfs_path": "Kan de LFS-root niet aanmaken op het opgegeven pad: %[1]s",
|
||||
"alert.asset_load_failed": "Het laden van hulp-bestanden vanuit {path} is mislukt. Controleer of de hulp-bestanden toegankelijk zijn.",
|
||||
"install.invalid_lfs_path": "Kan de LFS-root niet aanmaken op de opgegeven locatie: %[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.",
|
||||
"mail.actions.successful_run_after_failure_subject": "Werkstroom %[1]s hersteld in repository %[2]s",
|
||||
"mail.actions.not_successful_run_subject": "Werkstroom %[1]s mislukt in repository %[2]s",
|
||||
"mail.actions.successful_run_after_failure": "Werkstroom %[1]s hersteld in repository %[2]s",
|
||||
"mail.actions.not_successful_run": "Werkstroom %[1]s mislukt in repository %[2]s",
|
||||
"mail.actions.successful_run_after_failure_subject": "Werkstroom %[1]s hersteld in repositorie %[2]s",
|
||||
"mail.actions.not_successful_run_subject": "Werkstroom %[1]s mislukt in repositorie %[2]s",
|
||||
"mail.actions.successful_run_after_failure": "Werkstroom %[1]s hersteld in repositorie %[2]s",
|
||||
"mail.actions.not_successful_run": "Werkstroom %[1]s mislukt in repositorie %[2]s",
|
||||
"mail.actions.run_info_cur_status": "De status van deze run: %[1]s (zojuist bijgewerkt van %[2]s)",
|
||||
"mail.actions.run_info_previous_status": "Status vorige run: %[1]s",
|
||||
"mail.actions.run_info_ref": "Branch: %[1]s (%[2]s)",
|
||||
"mail.actions.run_info_trigger": "Getriggerd omdat: %[1]s door: %[2]s",
|
||||
"discussion.locked": "Deze discussie is gesloten. Commentaar is beperkt tot bijdragers.",
|
||||
"discussion.locked": "Deze discussie is afgesloten. Commentaar is alleen mogelijk voor bijdragers.",
|
||||
"relativetime.now": "nu",
|
||||
"relativetime.future": "in de toekomst",
|
||||
"repo.form.cannot_create": "Alle spaces waarin u repositories kan maken hebben hun maximum aantal repositories bereikt.",
|
||||
"repo.form.cannot_create": "Alle ruimtes waarin u repositories kan maken hebben hun maximum aantal repositories bereikt.",
|
||||
"moderation.report_content": "Meldt content",
|
||||
"moderation.report_abuse_form.header": "Meld misbruik bij de beheerder",
|
||||
"moderation.report_abuse_form.invalid": "Ongeldige argumenten",
|
||||
|
@ -86,5 +86,11 @@
|
|||
"relativetime.hours": {
|
||||
"one": "%d uur geleden",
|
||||
"other": "%d uren geleden"
|
||||
}
|
||||
},
|
||||
"repo.issue_indexer.title": "Issue indexer",
|
||||
"stars.list.none": "Niemand heeft deze repo een ster gegeven.",
|
||||
"watch.list.none": "Niemand houdt deze repo in de gaten.",
|
||||
"followers.incoming.list.self.none": "Niemand volgt uw profiel.",
|
||||
"followers.incoming.list.none": "Deze gebruiker wordt door niemand gevolgd.",
|
||||
"followers.outgoing.list.self.none": "U volgt niemand."
|
||||
}
|
||||
|
|
|
@ -98,5 +98,7 @@
|
|||
"followers.incoming.list.none": "Ninguém está seguindo este perfil.",
|
||||
"followers.outgoing.list.none": "%s não está seguindo ninguém.",
|
||||
"stars.list.none": "Ninguém favoritou este repositório.",
|
||||
"followers.outgoing.list.self.none": "Você não está seguindo ninguém."
|
||||
"followers.outgoing.list.self.none": "Você não está seguindo ninguém.",
|
||||
"editor.textarea.tab_hint": "Linha já indentada. Pressione <kbd>Tab</kbd> novamente ou <kbd>Esc</kbd> para sair do editor.",
|
||||
"editor.textarea.shift_tab_hint": "Sem indentação nesta linha. Pressione <kbd>Shift</kbd> + <kbd>Tab</kbd> novamente ou <kbd>Esc</kbd> para sair do editor."
|
||||
}
|
||||
|
|
|
@ -98,5 +98,7 @@
|
|||
"moderation.reported_thank_you": "Спасибо за ваше сообщение. Администрация оповещена.",
|
||||
"moderation.report_abuse_form.details": "Через эту форму можно жаловаться на пользователей, распространяющих спам или ведущих себя неадекватно.",
|
||||
"moderation.report_abuse_form.invalid": "Невалидные аргументы",
|
||||
"moderation.report_abuse_form.header": "Жалоба администрации"
|
||||
"moderation.report_abuse_form.header": "Жалоба администрации",
|
||||
"editor.textarea.tab_hint": "Отступ уже добавлен. Нажмите <kbd>Tab</kbd> снова или <kbd>Escape</kbd>, чтобы покинуть редактор.",
|
||||
"editor.textarea.shift_tab_hint": "В строке нет отступов. Нажмите <kbd>Shift</kbd> + <kbd>Tab</kbd> снова или <kbd>Escape</kbd>, чтобы покинуть редактор."
|
||||
}
|
||||
|
|
|
@ -66,5 +66,7 @@
|
|||
"followers.outgoing.list.self.none": "你没有关注任何人。",
|
||||
"followers.outgoing.list.none": "%s 没有关注任何人。",
|
||||
"stars.list.none": "没有人点赞这个仓库。",
|
||||
"followers.incoming.list.self.none": "没有人关注你的个人资料。"
|
||||
"followers.incoming.list.self.none": "没有人关注你的个人资料。",
|
||||
"editor.textarea.tab_hint": "此行已缩进。再次按 <kbd>Tab</kbd> 或按 <kbd>Escape</kbd> 退出编辑器。",
|
||||
"editor.textarea.shift_tab_hint": "此行无缩进。再次按 <kbd>Shift</kbd> + <kbd>Tab</kbd> 或按 <kbd>Escape</kbd> 退出编辑器。"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,71 @@
|
|||
{
|
||||
"repo.pulls.merged_title_desc": "將 %[1]d 次提交從 <code>%[2]s</code> 合併至 <code>%[3]s</code> %[4]s",
|
||||
"repo.pulls.title_desc": "請求將 %[1]d 次程式碼提交從 <code>%[2]s</code> 合併至 <code id=\"%[4]s\">%[3]s</code>",
|
||||
"search.milestone_kind": "搜尋里程碑…"
|
||||
"search.milestone_kind": "搜尋里程碑…",
|
||||
"home.welcome.no_activity": "沒有活動",
|
||||
"home.welcome.activity_hint": "您的動態摘要目前沒有任何內容。 您對關注的儲存庫所做的操作與活動將會顯示在這裡。",
|
||||
"stars.list.none": "沒有人標星這個儲存庫。",
|
||||
"watch.list.none": "沒有人關注這個儲存庫。",
|
||||
"home.explore_repos": "探索儲存庫",
|
||||
"home.explore_users": "探索使用者",
|
||||
"home.explore_orgs": "探索組織",
|
||||
"alert.range_error": " 必須是一個介於 %[1]s 和 %[2]s 之間的數字。",
|
||||
"install.invalid_lfs_path": "無法在指定路徑建立 LFS 根目錄:%[1]s",
|
||||
"relativetime.now": "現在",
|
||||
"relativetime.future": "未來",
|
||||
"repo.form.cannot_create": "您可以建立儲存庫的所有空間都已達到儲存庫上限。",
|
||||
"repo.issue_indexer.title": "問題索引器",
|
||||
"moderation.abuse_category": "分類",
|
||||
"moderation.abuse_category.placeholder": "選擇分類",
|
||||
"moderation.abuse_category.spam": "垃圾訊息",
|
||||
"moderation.abuse_category.malware": "惡意軟體",
|
||||
"moderation.abuse_category.illegal_content": "違法內容",
|
||||
"moderation.report_remarks.placeholder": "請提供您所檢舉濫用行為的相關細節。",
|
||||
"moderation.report_remarks": "備註",
|
||||
"moderation.submit_report": "提交檢舉",
|
||||
"moderation.reporting_failed": "無法提交新的濫用回報:%v",
|
||||
"moderation.reported_thank_you": "感謝您的回報,管理團隊已收到相關通知。",
|
||||
"mail.actions.successful_run_after_failure_subject": "儲存庫 %[2]s 中的工作流程 %[1]s 已恢復",
|
||||
"error.not_found.title": "找不到頁面",
|
||||
"incorrect_root_url": "這個 Forgejo 實例設定為在 \"%s\" 上提供服務。您目前是透過不同的 URL 存取 Forgejo,這可能會導致部分功能無法正常運作。正式的 URL 是由 Forgejo 管理員透過 app.ini 中的 ROOT_URL 設定所控制。",
|
||||
"themes.names.forgejo-auto": "Forgejo(遵循系統主題)",
|
||||
"themes.names.forgejo-light": "Forgejo 淺色",
|
||||
"themes.names.forgejo-dark": "Forgejo 深色",
|
||||
"followers.incoming.list.self.none": "沒有人關注您的個人資料。",
|
||||
"followers.incoming.list.none": "沒有人關注這位使用者。",
|
||||
"followers.outgoing.list.none": "%s 沒有追蹤任何人。",
|
||||
"followers.outgoing.list.self.none": "您沒有追蹤任何人。",
|
||||
"relativetime.mins": "%d 分鐘前",
|
||||
"relativetime.hours": "%d 小時前",
|
||||
"relativetime.days": "%d 天前",
|
||||
"relativetime.weeks": "%d 周前",
|
||||
"relativetime.months": "%d 個月前",
|
||||
"relativetime.years": "%d 年前",
|
||||
"relativetime.1day": "昨天",
|
||||
"relativetime.2days": "兩天前",
|
||||
"relativetime.1week": "上週",
|
||||
"relativetime.2weeks": "兩週前",
|
||||
"relativetime.1month": "上個月",
|
||||
"relativetime.2months": "兩個月前",
|
||||
"relativetime.1year": "去年",
|
||||
"relativetime.2years": "兩年前",
|
||||
"moderation.abuse_category.other_violations": "其他違反平台規則的行為",
|
||||
"mail.actions.not_successful_run_subject": "儲存庫 %[2]s 中的工作流程 %[1]s 已失敗",
|
||||
"mail.actions.successful_run_after_failure": "儲存庫 %[2]s 中的工作流程 %[1]s 已恢復",
|
||||
"mail.actions.not_successful_run": "儲存庫 %[2]s 中的工作流程 %[1]s 已失敗",
|
||||
"mail.actions.run_info_cur_status": "本次執行狀態:%[1]s(剛從 %[2]s 更新)",
|
||||
"mail.actions.run_info_previous_status": "前一次執行狀態:%[1]s",
|
||||
"mail.actions.run_info_ref": "分支:%[1]s (%[2]s)",
|
||||
"mail.actions.run_info_trigger": "觸發原因:%[1]s,由 %[2]s 執行",
|
||||
"discussion.locked": "此討論已被鎖定。僅限貢獻者留言。",
|
||||
"alert.asset_load_failed": "無法從 {path} 載入資源檔案。請確保這些資源檔案可以被存取。",
|
||||
"editor.textarea.tab_hint": "此行已縮排。再次按下 <kbd>Tab</kbd> 鍵或按下 <kbd>Escape</kbd> 鍵以離開編輯器。",
|
||||
"editor.textarea.shift_tab_hint": "此行未縮排。請再次按下 <kbd>Shift</kbd> + <kbd>Tab</kbd>,或按下 <kbd>Escape</kbd> 鍵以離開編輯器。",
|
||||
"admin.config.moderation_config": "審核設定",
|
||||
"moderation.report_abuse": "回報濫用行為",
|
||||
"moderation.report_content": "檢舉內容",
|
||||
"moderation.report_abuse_form.header": "向管理員回報濫用",
|
||||
"moderation.report_abuse_form.details": "這個表單是用來檢舉用戶建立垃圾帳號、儲存庫、問題、留言,或其他不當行為。",
|
||||
"moderation.report_abuse_form.invalid": "無效參數",
|
||||
"moderation.report_abuse_form.already_reported": "您已檢舉此內容"
|
||||
}
|
||||
|
|
1726
package-lock.json
generated
1726
package-lock.json
generated
File diff suppressed because it is too large
Load diff
39
package.json
39
package.json
|
@ -36,7 +36,7 @@
|
|||
"monaco-editor": "0.52.2",
|
||||
"monaco-editor-webpack-plugin": "7.1.0",
|
||||
"pdfobject": "2.3.0",
|
||||
"postcss": "8.5.2",
|
||||
"postcss": "8.5.4",
|
||||
"postcss-loader": "8.1.1",
|
||||
"postcss-nesting": "13.0.1",
|
||||
"pretty-ms": "9.0.0",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vue": "3.5.14",
|
||||
"vue": "3.5.16",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue3-calendar-heatmap": "2.0.5",
|
||||
|
@ -59,47 +59,46 @@
|
|||
"wrap-ansi": "9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "4.10.1",
|
||||
"@axe-core/playwright": "4.10.2",
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
|
||||
"@playwright/test": "1.52.0",
|
||||
"@stoplight/spectral-cli": "6.15.0",
|
||||
"@stylistic/eslint-plugin-js": "4.2.0",
|
||||
"@stylistic/eslint-plugin-js": "4.4.1",
|
||||
"@stylistic/stylelint-plugin": "3.1.2",
|
||||
"@typescript-eslint/parser": "8.31.1",
|
||||
"@vitejs/plugin-vue": "5.2.3",
|
||||
"@vitest/coverage-v8": "3.1.2",
|
||||
"@vitest/eslint-plugin": "1.1.43",
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"@vitest/coverage-v8": "3.2.3",
|
||||
"@vitest/eslint-plugin": "1.2.1",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"eslint": "9.25.1",
|
||||
"eslint-import-resolver-typescript": "4.3.4",
|
||||
"eslint": "9.28.0",
|
||||
"eslint-import-resolver-typescript": "4.4.3",
|
||||
"eslint-plugin-array-func": "5.0.2",
|
||||
"eslint-plugin-import-x": "4.11.0",
|
||||
"eslint-plugin-import-x": "4.15.1",
|
||||
"eslint-plugin-no-jquery": "3.1.1",
|
||||
"eslint-plugin-no-use-extend-native": "0.7.2",
|
||||
"eslint-plugin-playwright": "2.2.0",
|
||||
"eslint-plugin-regexp": "2.7.0",
|
||||
"eslint-plugin-regexp": "2.9.0",
|
||||
"eslint-plugin-sonarjs": "3.0.2",
|
||||
"eslint-plugin-unicorn": "59.0.0",
|
||||
"eslint-plugin-unicorn": "59.0.1",
|
||||
"eslint-plugin-toml": "0.12.0",
|
||||
"eslint-plugin-vitest-globals": "1.5.0",
|
||||
"eslint-plugin-vue": "10.1.0",
|
||||
"eslint-plugin-vue-scoped-css": "2.9.0",
|
||||
"eslint-plugin-vue": "10.2.0",
|
||||
"eslint-plugin-vue-scoped-css": "2.10.0",
|
||||
"eslint-plugin-wc": "2.2.1",
|
||||
"globals": "16.1.0",
|
||||
"happy-dom": "17.4.6",
|
||||
"happy-dom": "17.6.3",
|
||||
"license-checker-rseidelsohn": "4.4.2",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"markdownlint-cli": "0.45.0",
|
||||
"postcss-html": "1.8.0",
|
||||
"sharp": "0.34.2",
|
||||
"stylelint": "16.19.1",
|
||||
"stylelint": "16.20.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.11",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.2.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.31.1",
|
||||
"typescript-eslint": "8.33.1",
|
||||
"vite-string-plugin": "1.3.4",
|
||||
"vitest": "3.1.2"
|
||||
"vitest": "3.2.3"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
|
|
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