From d727757cfb3eb84e58f613fb0898047d87ff1d8f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 26 Sep 2024 08:03:00 +0000 Subject: [PATCH 001/166] Update dependency monaco-editor to v0.51.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7dd1cba03a..7a537c3fc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "mermaid": "11.2.1", "mini-css-extract-plugin": "2.9.1", "minimatch": "10.0.1", - "monaco-editor": "0.50.0", + "monaco-editor": "0.51.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", "postcss": "8.4.47", @@ -12114,9 +12114,9 @@ } }, "node_modules/monaco-editor": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.50.0.tgz", - "integrity": "sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA==", + "version": "0.51.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.51.0.tgz", + "integrity": "sha512-xaGwVV1fq343cM7aOYB6lVE4Ugf0UyimdD/x5PWcWBMKENwectaEu77FAN7c5sFiyumqeJdX1RPTh1ocioyDjw==", "license": "MIT" }, "node_modules/monaco-editor-webpack-plugin": { diff --git a/package.json b/package.json index 12c6a05c12..085d10dfca 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "mermaid": "11.2.1", "mini-css-extract-plugin": "2.9.1", "minimatch": "10.0.1", - "monaco-editor": "0.50.0", + "monaco-editor": "0.51.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", "postcss": "8.4.47", From 300e01f7337e7999870444b682f8036b1b6cd71e Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 15 Sep 2024 00:40:36 +0800 Subject: [PATCH 002/166] Check if the `due_date` is nil when editing issues (#32035) (cherry picked from commit 3a51c37672d2fbad1f222922e75ce704d5a1ac71) (cherry picked from commit 961766744bb5b7bf138cb508c8f9afe69622d514) --- routers/api/v1/repo/issue.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index afcfbc00e3..22779e38d2 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -839,10 +839,16 @@ func EditIssue(ctx *context.APIContext) { if (form.Deadline != nil || form.RemoveDeadline != nil) && canWrite { var deadlineUnix timeutil.TimeStamp - if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() { - deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(), - 23, 59, 59, 0, form.Deadline.Location()) - deadlineUnix = timeutil.TimeStamp(deadline.Unix()) + if form.RemoveDeadline == nil || !*form.RemoveDeadline { + if form.Deadline == nil { + ctx.Error(http.StatusBadRequest, "", "The due_date cannot be empty") + return + } + if !form.Deadline.IsZero() { + deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(), + 23, 59, 59, 0, form.Deadline.Location()) + deadlineUnix = timeutil.TimeStamp(deadline.Unix()) + } } if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil { From 232179aa3d25a554c8acdac29d13c3d82d14da92 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 16 Sep 2024 23:10:33 +0200 Subject: [PATCH 003/166] Do not escape relative path in RPM primary index (#32038) Fixes #32021 Do not escape the relative path. (cherry picked from commit f528df944bb9436afcb9272add2ee0cccefbdb55) (cherry picked from commit 0cafec4c7a2faf810953e9d522faf5dc019e1522) --- services/packages/rpm/repository.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go index 8a2db8670f..2cea04212a 100644 --- a/services/packages/rpm/repository.go +++ b/services/packages/rpm/repository.go @@ -13,7 +13,6 @@ import ( "errors" "fmt" "io" - "net/url" "strings" "time" @@ -440,7 +439,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] Archive: pd.FileMetadata.ArchiveSize, }, Location: Location{ - Href: fmt.Sprintf("package/%s/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(packageVersion), url.PathEscape(pd.FileMetadata.Architecture), url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture))), + Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture, pd.Package.Name, packageVersion, pd.FileMetadata.Architecture), }, Format: Format{ License: pd.VersionMetadata.License, From 84718e7b17a2b16e382d69528441a6ce1d4959a9 Mon Sep 17 00:00:00 2001 From: hiifong Date: Wed, 18 Sep 2024 03:02:48 +0800 Subject: [PATCH 004/166] Lazy load avatar images (#32051) (cherry picked from commit f38e1014483b84f4541ffb354cd5dfdd7e000e2c) (cherry picked from commit 9d5f409a5a0ac784523cc94a6011f8c8a41d5a95) --- modules/templates/util_avatar.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go index 85832cf264..afc1091516 100644 --- a/modules/templates/util_avatar.go +++ b/modules/templates/util_avatar.go @@ -34,7 +34,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML { name = "avatar" } - return template.HTML(``) + return template.HTML(``) } // Avatar renders user avatars. args: user, size (int), class (string) From 1a8f1482af1a2ce6db834914edfcf00f8dfb0683 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sun, 22 Sep 2024 09:28:20 +0200 Subject: [PATCH 005/166] feat: add IfZero utility function (cherry picked from commit 43de021ac1ca017212ec75fd88a8a80a9db27c4c) (cherry picked from commit 1bdf334844f398d0a2adafc4499cef15f95df6d8) --- modules/util/util.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/util/util.go b/modules/util/util.go index 0444680228..dcd7cf4f29 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -225,6 +225,15 @@ func Iif[T any](condition bool, trueVal, falseVal T) T { return falseVal } +// IfZero returns "def" if "v" is a zero value, otherwise "v" +func IfZero[T comparable](v, def T) T { + var zero T + if v == zero { + return def + } + return v +} + func ReserveLineBreakForTextarea(input string) string { // Since the content is from a form which is a textarea, the line endings are \r\n. // It's a standard behavior of HTML. From d26b7902ec03b3ae9454163a01b184d01d8217d0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 Sep 2024 15:17:25 +0800 Subject: [PATCH 006/166] Refactor CSRF protector (#32057) Remove unused CSRF options, decouple "new csrf protector" and "prepare" logic, do not redirect to home page if CSRF validation falis (it shouldn't happen in daily usage, if it happens, redirecting to home doesn't help either but just makes the problem more complex for "fetch") (cherry picked from commit 1fede04b83288d8a91304a83b7601699bb5cba04) Conflicts: options/locale/locale_en-US.ini tests/integration/repo_branch_test.go trivial context conflicts (cherry picked from commit 1ae3b127fc74906f0722caf6486890cf32d23715) --- options/locale/locale_en-US.ini | 1 - routers/web/web.go | 2 + services/context/context.go | 6 +- services/context/csrf.go | 192 ++++++++------------------ tests/integration/attachment_test.go | 3 +- tests/integration/csrf_test.go | 26 +--- tests/integration/repo_branch_test.go | 12 +- 7 files changed, 71 insertions(+), 171 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6b732fb121..61a820774d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -231,7 +231,6 @@ string.desc = Z - A [error] occurred = An error occurred report_message = If you believe that this is a Forgejo bug, please search for issues on Codeberg or open a new issue if necessary. -invalid_csrf = Bad Request: invalid CSRF token not_found = The target couldn't be found. network_error = Network error server_internal = Internal server error diff --git a/routers/web/web.go b/routers/web/web.go index d174b4e251..39116b882d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -132,6 +132,8 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) { // ensure the session uid is deleted _ = ctx.Session.Delete("uid") } + + ctx.Csrf.PrepareForSessionUser(ctx) } } diff --git a/services/context/context.go b/services/context/context.go index c0819ab11e..91e7b1849d 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -127,10 +127,8 @@ func Contexter() func(next http.Handler) http.Handler { csrfOpts := CsrfOptions{ Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), Cookie: setting.CSRFCookieName, - SetCookie: true, Secure: setting.SessionConfig.Secure, CookieHTTPOnly: setting.CSRFCookieHTTPOnly, - Header: "X-Csrf-Token", CookieDomain: setting.SessionConfig.Domain, CookiePath: setting.SessionConfig.CookiePath, SameSite: setting.SessionConfig.SameSite, @@ -156,7 +154,7 @@ func Contexter() func(next http.Handler) http.Handler { ctx.Base.AppendContextValue(WebContextKey, ctx) ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo }) - ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx) + ctx.Csrf = NewCSRFProtector(csrfOpts) // Get the last flash message from cookie lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash) @@ -193,8 +191,6 @@ func Contexter() func(next http.Handler) http.Handler { ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) ctx.Data["SystemConfig"] = setting.Config() - ctx.Data["CsrfToken"] = ctx.Csrf.GetToken() - ctx.Data["CsrfTokenHtml"] = template.HTML(``) // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations diff --git a/services/context/csrf.go b/services/context/csrf.go index 57c55e6550..5890d53f42 100644 --- a/services/context/csrf.go +++ b/services/context/csrf.go @@ -20,64 +20,42 @@ package context import ( - "encoding/base32" - "fmt" + "html/template" "net/http" "strconv" "time" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/web/middleware" +) + +const ( + CsrfHeaderName = "X-Csrf-Token" + CsrfFormName = "_csrf" ) // CSRFProtector represents a CSRF protector and is used to get the current token and validate the token. type CSRFProtector interface { - // GetHeaderName returns HTTP header to search for token. - GetHeaderName() string - // GetFormName returns form value to search for token. - GetFormName() string - // GetToken returns the token. - GetToken() string - // Validate validates the token in http context. + // PrepareForSessionUser prepares the csrf protector for the current session user. + PrepareForSessionUser(ctx *Context) + // Validate validates the csrf token in http context. Validate(ctx *Context) - // DeleteCookie deletes the cookie + // DeleteCookie deletes the csrf cookie DeleteCookie(ctx *Context) } type csrfProtector struct { opt CsrfOptions - // Token generated to pass via header, cookie, or hidden form value. - Token string - // This value must be unique per user. - ID string -} - -// GetHeaderName returns the name of the HTTP header for csrf token. -func (c *csrfProtector) GetHeaderName() string { - return c.opt.Header -} - -// GetFormName returns the name of the form value for csrf token. -func (c *csrfProtector) GetFormName() string { - return c.opt.Form -} - -// GetToken returns the current token. This is typically used -// to populate a hidden form in an HTML template. -func (c *csrfProtector) GetToken() string { - return c.Token + // id must be unique per user. + id string + // token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value. + token string } // CsrfOptions maintains options to manage behavior of Generate. type CsrfOptions struct { // The global secret value used to generate Tokens. Secret string - // HTTP header used to set and get token. - Header string - // Form value used to set and get token. - Form string // Cookie value used to set and get token. Cookie string // Cookie domain. @@ -87,103 +65,64 @@ type CsrfOptions struct { CookieHTTPOnly bool // SameSite set the cookie SameSite type SameSite http.SameSite - // Key used for getting the unique ID per user. - SessionKey string - // oldSessionKey saves old value corresponding to SessionKey. - oldSessionKey string - // If true, send token via X-Csrf-Token header. - SetHeader bool - // If true, send token via _csrf cookie. - SetCookie bool // Set the Secure flag to true on the cookie. Secure bool - // Disallow Origin appear in request header. - Origin bool - // Cookie lifetime. Default is 0 - CookieLifeTime int + // sessionKey is the key used for getting the unique ID per user. + sessionKey string + // oldSessionKey saves old value corresponding to sessionKey. + oldSessionKey string } -func prepareDefaultCsrfOptions(opt CsrfOptions) CsrfOptions { - if opt.Secret == "" { - randBytes, err := util.CryptoRandomBytes(8) - if err != nil { - // this panic can be handled by the recover() in http handlers - panic(fmt.Errorf("failed to generate random bytes: %w", err)) - } - opt.Secret = base32.StdEncoding.EncodeToString(randBytes) - } - if opt.Header == "" { - opt.Header = "X-Csrf-Token" - } - if opt.Form == "" { - opt.Form = "_csrf" - } - if opt.Cookie == "" { - opt.Cookie = "_csrf" - } - if opt.CookiePath == "" { - opt.CookiePath = "/" - } - if opt.SessionKey == "" { - opt.SessionKey = "uid" - } - if opt.CookieLifeTime == 0 { - opt.CookieLifeTime = int(CsrfTokenTimeout.Seconds()) - } - - opt.oldSessionKey = "_old_" + opt.SessionKey - return opt -} - -func newCsrfCookie(c *csrfProtector, value string) *http.Cookie { +func newCsrfCookie(opt *CsrfOptions, value string) *http.Cookie { return &http.Cookie{ - Name: c.opt.Cookie, + Name: opt.Cookie, Value: value, - Path: c.opt.CookiePath, - Domain: c.opt.CookieDomain, - MaxAge: c.opt.CookieLifeTime, - Secure: c.opt.Secure, - HttpOnly: c.opt.CookieHTTPOnly, - SameSite: c.opt.SameSite, + Path: opt.CookiePath, + Domain: opt.CookieDomain, + MaxAge: int(CsrfTokenTimeout.Seconds()), + Secure: opt.Secure, + HttpOnly: opt.CookieHTTPOnly, + SameSite: opt.SameSite, } } -// PrepareCSRFProtector returns a CSRFProtector to be used for every request. -// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie. -func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector { - opt = prepareDefaultCsrfOptions(opt) - x := &csrfProtector{opt: opt} - - if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 { - return x +func NewCSRFProtector(opt CsrfOptions) CSRFProtector { + if opt.Secret == "" { + panic("CSRF secret is empty but it must be set") // it shouldn't happen because it is always set in code } + opt.Cookie = util.IfZero(opt.Cookie, "_csrf") + opt.CookiePath = util.IfZero(opt.CookiePath, "/") + opt.sessionKey = "uid" + opt.oldSessionKey = "_old_" + opt.sessionKey + return &csrfProtector{opt: opt} +} - x.ID = "0" - uidAny := ctx.Session.Get(opt.SessionKey) - if uidAny != nil { +func (c *csrfProtector) PrepareForSessionUser(ctx *Context) { + c.id = "0" + if uidAny := ctx.Session.Get(c.opt.sessionKey); uidAny != nil { switch uidVal := uidAny.(type) { case string: - x.ID = uidVal + c.id = uidVal case int64: - x.ID = strconv.FormatInt(uidVal, 10) + c.id = strconv.FormatInt(uidVal, 10) default: log.Error("invalid uid type in session: %T", uidAny) } } - oldUID := ctx.Session.Get(opt.oldSessionKey) - uidChanged := oldUID == nil || oldUID.(string) != x.ID - cookieToken := ctx.GetSiteCookie(opt.Cookie) + oldUID := ctx.Session.Get(c.opt.oldSessionKey) + uidChanged := oldUID == nil || oldUID.(string) != c.id + cookieToken := ctx.GetSiteCookie(c.opt.Cookie) needsNew := true if uidChanged { - _ = ctx.Session.Set(opt.oldSessionKey, x.ID) + _ = ctx.Session.Set(c.opt.oldSessionKey, c.id) } else if cookieToken != "" { // If cookie token presents, reuse existing unexpired token, else generate a new one. if issueTime, ok := ParseCsrfToken(cookieToken); ok { dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time. if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval { - x.Token = cookieToken + c.token = cookieToken needsNew = false } } @@ -191,42 +130,33 @@ func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector { if needsNew { // FIXME: actionId. - x.Token = GenerateCsrfToken(x.opt.Secret, x.ID, "POST", time.Now()) - if opt.SetCookie { - cookie := newCsrfCookie(x, x.Token) - ctx.Resp.Header().Add("Set-Cookie", cookie.String()) - } + c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now()) + cookie := newCsrfCookie(&c.opt, c.token) + ctx.Resp.Header().Add("Set-Cookie", cookie.String()) } - if opt.SetHeader { - ctx.Resp.Header().Add(opt.Header, x.Token) - } - return x + ctx.Data["CsrfToken"] = c.token + ctx.Data["CsrfTokenHtml"] = template.HTML(``) } func (c *csrfProtector) validateToken(ctx *Context, token string) { - if !ValidCsrfToken(token, c.opt.Secret, c.ID, "POST", time.Now()) { + if !ValidCsrfToken(token, c.opt.Secret, c.id, "POST", time.Now()) { c.DeleteCookie(ctx) - if middleware.IsAPIPath(ctx.Req) { - // currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints. - http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest) - } else { - ctx.Flash.Error(ctx.Tr("error.invalid_csrf")) - ctx.Redirect(setting.AppSubURL + "/") - } + // currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints. + // FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch) + http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest) } } // Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token" // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated. -// If this validation fails, custom Error is sent in the reply. -// If neither a header nor form value is found, http.StatusBadRequest is sent. +// If this validation fails, http.StatusBadRequest is sent. func (c *csrfProtector) Validate(ctx *Context) { - if token := ctx.Req.Header.Get(c.GetHeaderName()); token != "" { + if token := ctx.Req.Header.Get(CsrfHeaderName); token != "" { c.validateToken(ctx, token) return } - if token := ctx.Req.FormValue(c.GetFormName()); token != "" { + if token := ctx.Req.FormValue(CsrfFormName); token != "" { c.validateToken(ctx, token) return } @@ -234,9 +164,7 @@ func (c *csrfProtector) Validate(ctx *Context) { } func (c *csrfProtector) DeleteCookie(ctx *Context) { - if c.opt.SetCookie { - cookie := newCsrfCookie(c, "") - cookie.MaxAge = -1 - ctx.Resp.Header().Add("Set-Cookie", cookie.String()) - } + cookie := newCsrfCookie(&c.opt, "") + cookie.MaxAge = -1 + ctx.Resp.Header().Add("Set-Cookie", cookie.String()) } diff --git a/tests/integration/attachment_test.go b/tests/integration/attachment_test.go index 95c9c9f753..7cbc2545d5 100644 --- a/tests/integration/attachment_test.go +++ b/tests/integration/attachment_test.go @@ -60,7 +60,8 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri func TestCreateAnonymousAttachment(t *testing.T) { defer tests.PrepareTestEnv(t)() session := emptyTestSession(t) - createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusSeeOther) + // this test is not right because it just doesn't pass the CSRF validation + createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusBadRequest) } func TestCreateIssueAttachment(t *testing.T) { diff --git a/tests/integration/csrf_test.go b/tests/integration/csrf_test.go index a789859889..fcb9661b8a 100644 --- a/tests/integration/csrf_test.go +++ b/tests/integration/csrf_test.go @@ -5,12 +5,10 @@ package integration import ( "net/http" - "strings" "testing" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -25,28 +23,12 @@ func TestCsrfProtection(t *testing.T) { req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ "_csrf": "fake_csrf", }) - session.MakeRequest(t, req, http.StatusSeeOther) - - resp := session.MakeRequest(t, req, http.StatusSeeOther) - loc := resp.Header().Get("Location") - assert.Equal(t, setting.AppSubURL+"/", loc) - resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - assert.Equal(t, "Bad Request: invalid CSRF token", - strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), - ) + resp := session.MakeRequest(t, req, http.StatusBadRequest) + assert.Contains(t, resp.Body.String(), "Invalid CSRF token") // test web form csrf via header. TODO: should use an UI api to test req = NewRequest(t, "POST", "/user/settings") req.Header.Add("X-Csrf-Token", "fake_csrf") - session.MakeRequest(t, req, http.StatusSeeOther) - - resp = session.MakeRequest(t, req, http.StatusSeeOther) - loc = resp.Header().Get("Location") - assert.Equal(t, setting.AppSubURL+"/", loc) - resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - assert.Equal(t, "Bad Request: invalid CSRF token", - strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), - ) + resp = session.MakeRequest(t, req, http.StatusBadRequest) + assert.Contains(t, resp.Body.String(), "Invalid CSRF token") } diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 2aa299479a..df9ea9a97c 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -18,7 +18,6 @@ import ( "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" repo_service "code.gitea.io/gitea/services/repository" @@ -157,15 +156,8 @@ func TestCreateBranchInvalidCSRF(t *testing.T) { "_csrf": "fake_csrf", "new_branch_name": "test", }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - loc := resp.Header().Get("Location") - assert.Equal(t, setting.AppSubURL+"/", loc) - resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - assert.Equal(t, - "Bad Request: invalid CSRF token", - strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), - ) + resp := session.MakeRequest(t, req, http.StatusBadRequest) + assert.Contains(t, resp.Body.String(), "Invalid CSRF token") } func TestDatabaseMissingABranch(t *testing.T) { From 5b6d8a303d3a6738657238983b75edf547ace0a3 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sun, 22 Sep 2024 12:57:03 +0200 Subject: [PATCH 007/166] Refactor CSRF protector (#32057) (fix forgejo tests) Fix the tests unique to Forgejo that are impacted by the refactor. (cherry picked from commit 6275d1bc5000a09793574696cb18cc5b5c4b07d6) --- services/context/csrf.go | 7 ++-- tests/integration/links_test.go | 24 +++++++++--- tests/integration/oauth_test.go | 66 ++++++++++++++++++--------------- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/services/context/csrf.go b/services/context/csrf.go index 5890d53f42..e0518a499b 100644 --- a/services/context/csrf.go +++ b/services/context/csrf.go @@ -30,8 +30,9 @@ import ( ) const ( - CsrfHeaderName = "X-Csrf-Token" - CsrfFormName = "_csrf" + CsrfHeaderName = "X-Csrf-Token" + CsrfFormName = "_csrf" + CsrfErrorString = "Invalid CSRF token." ) // CSRFProtector represents a CSRF protector and is used to get the current token and validate the token. @@ -144,7 +145,7 @@ func (c *csrfProtector) validateToken(ctx *Context, token string) { c.DeleteCookie(ctx) // currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints. // FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch) - http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest) + http.Error(ctx.Resp, CsrfErrorString, http.StatusBadRequest) } } diff --git a/tests/integration/links_test.go b/tests/integration/links_test.go index 68d7008e02..e9ad933b24 100644 --- a/tests/integration/links_test.go +++ b/tests/integration/links_test.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" + forgejo_context "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -190,11 +191,6 @@ func TestRedirectsWebhooks(t *testing.T) { {from: "/user/settings/hooks/" + kind + "/new", to: "/user/login", verb: "GET"}, {from: "/admin/system-hooks/" + kind + "/new", to: "/user/login", verb: "GET"}, {from: "/admin/default-hooks/" + kind + "/new", to: "/user/login", verb: "GET"}, - {from: "/user2/repo1/settings/hooks/" + kind + "/new", to: "/", verb: "POST"}, - {from: "/admin/system-hooks/" + kind + "/new", to: "/", verb: "POST"}, - {from: "/admin/default-hooks/" + kind + "/new", to: "/", verb: "POST"}, - {from: "/user2/repo1/settings/hooks/1", to: "/", verb: "POST"}, - {from: "/admin/hooks/1", to: "/", verb: "POST"}, } for _, info := range redirects { req := NewRequest(t, info.verb, info.from) @@ -202,6 +198,24 @@ func TestRedirectsWebhooks(t *testing.T) { assert.EqualValues(t, path.Join(setting.AppSubURL, info.to), test.RedirectURL(resp), info.from) } } + + for _, kind := range []string{"forgejo", "gitea"} { + csrf := []struct { + from string + verb string + }{ + {from: "/user2/repo1/settings/hooks/" + kind + "/new", verb: "POST"}, + {from: "/admin/hooks/1", verb: "POST"}, + {from: "/admin/system-hooks/" + kind + "/new", verb: "POST"}, + {from: "/admin/default-hooks/" + kind + "/new", verb: "POST"}, + {from: "/user2/repo1/settings/hooks/1", verb: "POST"}, + } + for _, info := range csrf { + req := NewRequest(t, info.verb, info.from) + resp := MakeRequest(t, req, http.StatusBadRequest) + assert.Contains(t, resp.Body.String(), forgejo_context.CsrfErrorString) + } + } } func TestRepoLinks(t *testing.T) { diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index 0d5e9a0472..f385b99e46 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "net/http" + "net/http/httptest" "net/url" "strings" "testing" @@ -24,6 +25,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/routers/web/auth" + forgejo_context "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" "github.com/markbates/goth" @@ -803,6 +805,16 @@ func TestOAuthIntrospection(t *testing.T) { }) } +func requireCookieCSRF(t *testing.T, resp http.ResponseWriter) string { + for _, c := range resp.(*httptest.ResponseRecorder).Result().Cookies() { + if c.Name == "_csrf" { + return c.Value + } + } + require.True(t, false, "_csrf not found in cookies") + return "" +} + func TestOAuth_GrantScopesReadUser(t *testing.T) { defer tests.PrepareTestEnv(t)() @@ -840,19 +852,18 @@ func TestOAuth_GrantScopesReadUser(t *testing.T) { authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther) authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0] - htmlDoc := NewHTMLParser(t, authorizeResp.Body) grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "client_id": app.ClientID, "redirect_uri": "a", "state": "thestate", "granted": "true", }) - grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther) - htmlDocGrant := NewHTMLParser(t, grantResp.Body) + grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest) + assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString) accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ - "_csrf": htmlDocGrant.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "grant_type": "authorization_code", "client_id": app.ClientID, "client_secret": app.ClientSecret, @@ -921,19 +932,18 @@ func TestOAuth_GrantScopesFailReadRepository(t *testing.T) { authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther) authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0] - htmlDoc := NewHTMLParser(t, authorizeResp.Body) grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "client_id": app.ClientID, "redirect_uri": "a", "state": "thestate", "granted": "true", }) - grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther) - htmlDocGrant := NewHTMLParser(t, grantResp.Body) + grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest) + assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString) accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ - "_csrf": htmlDocGrant.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "grant_type": "authorization_code", "client_id": app.ClientID, "client_secret": app.ClientSecret, @@ -1000,19 +1010,18 @@ func TestOAuth_GrantScopesReadRepository(t *testing.T) { authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther) authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0] - htmlDoc := NewHTMLParser(t, authorizeResp.Body) grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "client_id": app.ClientID, "redirect_uri": "a", "state": "thestate", "granted": "true", }) - grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther) - htmlDocGrant := NewHTMLParser(t, grantResp.Body) + grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest) + assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString) accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ - "_csrf": htmlDocGrant.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "grant_type": "authorization_code", "client_id": app.ClientID, "client_secret": app.ClientSecret, @@ -1082,19 +1091,18 @@ func TestOAuth_GrantScopesReadPrivateGroups(t *testing.T) { authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther) authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0] - htmlDoc := NewHTMLParser(t, authorizeResp.Body) grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "client_id": app.ClientID, "redirect_uri": "a", "state": "thestate", "granted": "true", }) - grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther) - htmlDocGrant := NewHTMLParser(t, grantResp.Body) + grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest) + assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString) accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ - "_csrf": htmlDocGrant.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "grant_type": "authorization_code", "client_id": app.ClientID, "client_secret": app.ClientSecret, @@ -1164,19 +1172,18 @@ func TestOAuth_GrantScopesReadOnlyPublicGroups(t *testing.T) { authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther) authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0] - htmlDoc := NewHTMLParser(t, authorizeResp.Body) grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "client_id": app.ClientID, "redirect_uri": "a", "state": "thestate", "granted": "true", }) - grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther) - htmlDocGrant := NewHTMLParser(t, grantResp.Body) + grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest) + assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString) accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ - "_csrf": htmlDocGrant.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "grant_type": "authorization_code", "client_id": app.ClientID, "client_secret": app.ClientSecret, @@ -1260,19 +1267,18 @@ func TestOAuth_GrantScopesReadPublicGroupsWithTheReadScope(t *testing.T) { authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther) authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0] - htmlDoc := NewHTMLParser(t, authorizeResp.Body) grantReq := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{ - "_csrf": htmlDoc.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "client_id": app.ClientID, "redirect_uri": "a", "state": "thestate", "granted": "true", }) - grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther) - htmlDocGrant := NewHTMLParser(t, grantResp.Body) + grantResp := ctx.MakeRequest(t, grantReq, http.StatusBadRequest) + assert.NotContains(t, grantResp.Body.String(), forgejo_context.CsrfErrorString) accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ - "_csrf": htmlDocGrant.GetCSRF(), + "_csrf": requireCookieCSRF(t, authorizeResp), "grant_type": "authorization_code", "client_id": app.ClientID, "client_secret": app.ClientSecret, From 2f1a737769b2ecd64c7c62e94cabf4bd72e89b69 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 20 Sep 2024 21:00:39 +0200 Subject: [PATCH 008/166] Fix incorrect `/tokens` api (#32085) Fixes #32078 - Add missing scopes output. - Disallow empty scope. --------- Co-authored-by: Lunny Xiao (cherry picked from commit 08adbc468f8875fd4763c3656b334203c11adc0a) (cherry picked from commit 526054332acb221e061d3900bba2dc6e012da52d) --- routers/api/v1/user/app.go | 5 +++++ tests/integration/api_token_test.go | 31 ++++++++++------------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index b61ebac7d0..d5b20f7703 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -118,6 +118,10 @@ func CreateAccessToken(ctx *context.APIContext) { ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err)) return } + if scope == "" { + ctx.Error(http.StatusBadRequest, "AccessTokenScope", "access token must have a scope") + return + } t.Scope = scope if err := auth_model.NewAccessToken(ctx, t); err != nil { @@ -129,6 +133,7 @@ func CreateAccessToken(ctx *context.APIContext) { Token: t.Token, ID: t.ID, TokenLastEight: t.TokenLastEight, + Scopes: t.Scope.StringSlice(), }) } diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index 9c7bf37330..01d18ef6f1 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -23,10 +23,10 @@ func TestAPICreateAndDeleteToken(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, nil) + newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) deleteAPIAccessToken(t, newAccessToken, user) - newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, nil) + newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) deleteAPIAccessToken(t, newAccessToken, user) } @@ -72,19 +72,19 @@ func TestAPIDeleteTokensPermission(t *testing.T) { user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // admin can delete tokens for other users - createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, nil) + createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1"). AddBasicAuth(admin.Name) MakeRequest(t, req, http.StatusNoContent) // non-admin can delete tokens for himself - createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, nil) + createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2"). AddBasicAuth(user2.Name) MakeRequest(t, req, http.StatusNoContent) // non-admin can't delete tokens for other users - createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, nil) + createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3"). AddBasicAuth(user4.Name) MakeRequest(t, req, http.StatusForbidden) @@ -520,7 +520,7 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model unauthorizedScopes = append(unauthorizedScopes, cateogoryUnauthorizedScopes...) } - accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, &unauthorizedScopes) + accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, unauthorizedScopes) defer deleteAPIAccessToken(t, accessToken, user) // Request the endpoint. Verify that permission is denied. @@ -532,20 +532,12 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model // createAPIAccessTokenWithoutCleanUp Create an API access token and assert that // creation succeeded. The caller is responsible for deleting the token. -func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes *[]auth_model.AccessTokenScope) api.AccessToken { +func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes []auth_model.AccessTokenScope) api.AccessToken { payload := map[string]any{ - "name": tokenName, - } - if scopes != nil { - for _, scope := range *scopes { - scopes, scopesExists := payload["scopes"].([]string) - if !scopesExists { - scopes = make([]string, 0) - } - scopes = append(scopes, string(scope)) - payload["scopes"] = scopes - } + "name": tokenName, + "scopes": scopes, } + log.Debug("Requesting creation of token with scopes: %v", scopes) req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", payload). AddBasicAuth(user.Name) @@ -563,8 +555,7 @@ func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *us return newAccessToken } -// createAPIAccessTokenWithoutCleanUp Delete an API access token and assert that -// deletion succeeded. +// deleteAPIAccessToken deletes an API access token and assert that deletion succeeded. func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_model.User) { req := NewRequestf(t, "DELETE", "/api/v1/users/"+user.LoginName+"/tokens/%d", accessToken.ID). AddBasicAuth(user.Name) From 81308159fd72fc901cdd4afa0b8b1193bc054532 Mon Sep 17 00:00:00 2001 From: Timon van der Berg Date: Sat, 21 Sep 2024 20:57:01 +0200 Subject: [PATCH 009/166] Repo Activity: count new issues that were closed (#31776) I'm new to go and contributing to gitea, your guidance is much appreciated. This is meant to solve https://github.com/go-gitea/gitea/issues/13309 Previously, closed issues would not be shown under new issues in the activity tab, even if they were newly created. changes: * Split out newlyCreatedIssues from issuesForActivityStatement to count both currently open and closed issues. * Use a seperate function to count active issues to prevent double-counting issues after the above change. Result is that new issues that have been closed are shown both under "new" and "closed". Signed-off-by: Timon van der Berg (cherry picked from commit ebfde845294cc681de6b1fe1adcf27e35f61b89b) (cherry picked from commit 2675a24649af2fff34f5c7e416d6ff78591d8d9c) --- models/activities/repo_activity.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/models/activities/repo_activity.go b/models/activities/repo_activity.go index ba5e4959f0..3ffad035b7 100644 --- a/models/activities/repo_activity.go +++ b/models/activities/repo_activity.go @@ -34,6 +34,7 @@ type ActivityStats struct { OpenedPRAuthorCount int64 MergedPRs issues_model.PullRequestList MergedPRAuthorCount int64 + ActiveIssues issues_model.IssueList OpenedIssues issues_model.IssueList OpenedIssueAuthorCount int64 ClosedIssues issues_model.IssueList @@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int { // ActiveIssueCount returns total active issue count func (stats *ActivityStats) ActiveIssueCount() int { - return stats.OpenedIssueCount() + stats.ClosedIssueCount() + return len(stats.ActiveIssues) } // OpenedIssueCount returns open issue count @@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi stats.ClosedIssueAuthorCount = count // New issues - sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false) + sess = newlyCreatedIssues(ctx, repoID, fromTime) sess.OrderBy("issue.created_unix ASC") stats.OpenedIssues = make(issues_model.IssueList, 0) if err = sess.Find(&stats.OpenedIssues); err != nil { return err } + // Active issues + sess = activeIssues(ctx, repoID, fromTime) + sess.OrderBy("issue.created_unix ASC") + stats.ActiveIssues = make(issues_model.IssueList, 0) + if err = sess.Find(&stats.ActiveIssues); err != nil { + return err + } + // Opened issue authors sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false) if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil { @@ -317,6 +326,23 @@ func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int return sess.Find(&stats.UnresolvedIssues) } +func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session { + sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). + And("issue.is_pull = ?", false). // Retain the is_pull check to exclude pull requests + And("issue.created_unix >= ?", fromTime.Unix()) // Include all issues created after fromTime + + return sess +} + +func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session { + sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). + And("issue.is_pull = ?", false). + And("issue.created_unix >= ?", fromTime.Unix()). + Or("issue.closed_unix >= ?", fromTime.Unix()) + + return sess +} + func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session { sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). And("issue.is_closed = ?", closed) From 6c16834d281fb66caf3e3f21817ef6464a781117 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 22 Sep 2024 05:56:25 +0800 Subject: [PATCH 010/166] Fix wrong last modify time (#32102) (cherry picked from commit a802508f88e546bf18990559e44bf27a09c869ee) (cherry picked from commit f709de24039ab7e605d3e09e3b61240836381603) --- modules/httpcache/httpcache.go | 3 ++- modules/httplib/serve.go | 1 + routers/api/packages/maven/maven.go | 4 +++- routers/web/repo/githttp.go | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index b4af371541..30ce0a4a03 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -76,7 +76,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s w.Header().Set("Etag", etag) } if lastModified != nil && !lastModified.IsZero() { - w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat)) + // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat + w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat)) } if len(etag) > 0 { diff --git a/modules/httplib/serve.go b/modules/httplib/serve.go index 6e147d76f5..2e3e6a7c42 100644 --- a/modules/httplib/serve.go +++ b/modules/httplib/serve.go @@ -79,6 +79,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) { httpcache.SetCacheControlInHeader(header, duration) if !opts.LastModified.IsZero() { + // 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)) } } diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index 58271e1d43..4181577454 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -117,7 +117,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) { xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...) latest := pds[len(pds)-1] - ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat)) + // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat + lastModifed := latest.Version.CreatedUnix.AsTime().UTC().Format(http.TimeFormat) + ctx.Resp.Header().Set("Last-Modified", lastModifed) ext := strings.ToLower(filepath.Ext(params.Filename)) if isChecksumExtension(ext) { diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 9f3b63698a..a082498dfd 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -395,7 +395,8 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string ctx.Resp.Header().Set("Content-Type", contentType) ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) - ctx.Resp.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat)) + // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat + ctx.Resp.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) http.ServeFile(ctx.Resp, ctx.Req, reqFile) } From 74712e340026f127bcc627cc2342fe5b629c5f00 Mon Sep 17 00:00:00 2001 From: Jamie Schouten Date: Sun, 22 Sep 2024 00:42:17 +0200 Subject: [PATCH 011/166] Add bin to Composer Metadata (#32099) This PR addresses the missing `bin` field in Composer metadata, which currently causes vendor-provided binaries to not be symlinked to `vendor/bin` during installation. In the current implementation, running `composer install` does not publish the binaries, leading to issues where expected binaries are not available. By properly declaring the `bin` field, this PR ensures that binaries are correctly symlinked upon installation, as described in the [Composer documentation](https://getcomposer.org/doc/articles/vendor-binaries.md). (cherry picked from commit d351a42494e71b5e2da63302c2f9b46c78e6dbde) (cherry picked from commit 9d3473119893ffde0ab36d98e7a0e41c5d0ba9a3) --- modules/packages/composer/metadata.go | 1 + tests/integration/api_packages_composer_test.go | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/packages/composer/metadata.go b/modules/packages/composer/metadata.go index 2c2e9ebf27..6035eae8ca 100644 --- a/modules/packages/composer/metadata.go +++ b/modules/packages/composer/metadata.go @@ -48,6 +48,7 @@ type Metadata struct { Homepage string `json:"homepage,omitempty"` License Licenses `json:"license,omitempty"` Authors []Author `json:"authors,omitempty"` + Bin []string `json:"bin,omitempty"` Autoload map[string]any `json:"autoload,omitempty"` AutoloadDev map[string]any `json:"autoload-dev,omitempty"` Extra map[string]any `json:"extra,omitempty"` diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index 9cdcd07e37..9d25cc4d64 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -37,6 +37,7 @@ func TestPackageComposer(t *testing.T) { packageType := "composer-plugin" packageAuthor := "Gitea Authors" packageLicense := "MIT" + packageBin := "./bin/script" var buf bytes.Buffer archive := zip.NewWriter(&buf) @@ -50,6 +51,9 @@ func TestPackageComposer(t *testing.T) { { "name": "` + packageAuthor + `" } + ], + "bin": [ + "` + packageBin + `" ] }`)) archive.Close() @@ -211,6 +215,8 @@ func TestPackageComposer(t *testing.T) { assert.Len(t, pkgs[0].Authors, 1) assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) assert.Equal(t, "zip", pkgs[0].Dist.Type) - assert.Equal(t, "7b40bfd6da811b2b78deec1e944f156dbb2c747b", pkgs[0].Dist.Checksum) + assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) + assert.Len(t, pkgs[0].Bin, 1) + assert.Equal(t, packageBin, pkgs[0].Bin[0]) }) } From ba7da0af3184e7f732ea5eec4a01c6fdd1b899ab Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 21 Sep 2024 17:50:54 +0800 Subject: [PATCH 012/166] Use camo.Always instead of camo.Allways (#32097) Fix #31575 https://gitea.com/gitea/docs/pulls/73 (cherry picked from commit 8e2dd5d3ddfb442937c79f05df88d18b856952cb) (cherry picked from commit 2ffb08bb888bb950b6ed4ff12a4cf4bacc337e18) --- custom/conf/app.example.ini | 3 ++- modules/markup/camo.go | 2 +- modules/markup/camo_test.go | 2 +- modules/setting/camo.go | 14 ++++++++++++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 9cb5a67172..2eff51fe98 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -529,7 +529,8 @@ INTERNAL_TOKEN = ;; HMAC to encode urls with, it **is required** if camo is enabled. ;HMAC_KEY = ;; Set to true to use camo for https too lese only non https urls are proxyed -;ALLWAYS = false +;; ALLWAYS is deprecated and will be removed in the future +;ALWAYS = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/markup/camo.go b/modules/markup/camo.go index e93797de2b..7e2583469d 100644 --- a/modules/markup/camo.go +++ b/modules/markup/camo.go @@ -38,7 +38,7 @@ func camoHandleLink(link string) string { if setting.Camo.Enabled { lnkURL, err := url.Parse(link) if err == nil && lnkURL.IsAbs() && !strings.HasPrefix(link, setting.AppURL) && - (setting.Camo.Allways || lnkURL.Scheme != "https") { + (setting.Camo.Always || lnkURL.Scheme != "https") { return CamoEncode(link) } } diff --git a/modules/markup/camo_test.go b/modules/markup/camo_test.go index ba58835221..3c5d40afa0 100644 --- a/modules/markup/camo_test.go +++ b/modules/markup/camo_test.go @@ -28,7 +28,7 @@ func TestCamoHandleLink(t *testing.T) { "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc", camoHandleLink("http://testimages.org/img.jpg")) - setting.Camo.Allways = true + setting.Camo.Always = true assert.Equal(t, "https://gitea.com/img.jpg", camoHandleLink("https://gitea.com/img.jpg")) diff --git a/modules/setting/camo.go b/modules/setting/camo.go index 366e9a116c..608ecf8363 100644 --- a/modules/setting/camo.go +++ b/modules/setting/camo.go @@ -3,18 +3,28 @@ package setting -import "code.gitea.io/gitea/modules/log" +import ( + "strconv" + + "code.gitea.io/gitea/modules/log" +) var Camo = struct { Enabled bool ServerURL string `ini:"SERVER_URL"` HMACKey string `ini:"HMAC_KEY"` - Allways bool + Always bool }{} func loadCamoFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "camo", &Camo) if Camo.Enabled { + oldValue := rootCfg.Section("camo").Key("ALLWAYS").MustString("") + if oldValue != "" { + log.Warn("camo.ALLWAYS is deprecated, use camo.ALWAYS instead") + Camo.Always, _ = strconv.ParseBool(oldValue) + } + if Camo.ServerURL == "" || Camo.HMACKey == "" { log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`) } From eb4f1de8ec64a078971de8be6070b81ce74b6c1d Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sun, 22 Sep 2024 10:01:43 +0200 Subject: [PATCH 013/166] chore(release-notes): weekly cherry-pick week 2024-39 (cherry picked from commit e3deb88a8d2494e386f221b9dc743a6a1710837d) --- release-notes/5372.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 release-notes/5372.md diff --git a/release-notes/5372.md b/release-notes/5372.md new file mode 100644 index 0000000000..fccb305f34 --- /dev/null +++ b/release-notes/5372.md @@ -0,0 +1,5 @@ +feat: [commit](https://codeberg.org/forgejo/forgejo/commit/9d3473119893ffde0ab36d98e7a0e41c5d0ba9a3) Add bin to Composer Metadata. +fix: [commit](https://codeberg.org/forgejo/forgejo/commit/f709de24039ab7e605d3e09e3b61240836381603) Fix wrong last modify time. +fix: [commit](https://codeberg.org/forgejo/forgejo/commit/2675a24649af2fff34f5c7e416d6ff78591d8d9c) Repo Activity: count new issues that were closed. +fix: [commit](https://codeberg.org/forgejo/forgejo/commit/526054332acb221e061d3900bba2dc6e012da52d) Fix incorrect /tokens api. +fix: [commit](https://codeberg.org/forgejo/forgejo/commit/0cafec4c7a2faf810953e9d522faf5dc019e1522) Do not escape relative path in RPM primary index. From 658ed564cbfd4b95e2985dc2e15bedad33e8a6ee Mon Sep 17 00:00:00 2001 From: Exploding Dragon Date: Fri, 27 Sep 2024 08:21:22 +0000 Subject: [PATCH 014/166] feat: add architecture-specific removal support for arch package (#5351) - [x] add architecture-specific removal support - [x] Fix upload competition - [x] Fix not checking input when downloading docs: https://codeberg.org/forgejo/docs/pulls/874 ### Release notes - [ ] I do not want this change to show in the release notes. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5351 Reviewed-by: Earl Warren Co-authored-by: Exploding Dragon Co-committed-by: Exploding Dragon (cherry picked from commit 89742c49135e47372e272596bd536d5d1244721f) --- modules/packages/arch/metadata.go | 69 ++++++++++----------- routers/api/packages/api.go | 12 ++-- routers/api/packages/arch/arch.go | 47 +++++++++----- services/packages/arch/repository.go | 65 ++++++++++--------- templates/package/content/arch.tmpl | 4 +- templates/package/metadata/arch.tmpl | 4 +- tests/integration/api_packages_arch_test.go | 50 ++++++++++++--- 7 files changed, 151 insertions(+), 100 deletions(-) diff --git a/modules/packages/arch/metadata.go b/modules/packages/arch/metadata.go index 0e08670311..6cdde75cdc 100644 --- a/modules/packages/arch/metadata.go +++ b/modules/packages/arch/metadata.go @@ -39,8 +39,8 @@ const ( var ( reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`) reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`) - reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?(:.*)?$`) - rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?[a-zA-Z0-9@._+-]+)?$`) + reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?(:.*)?$`) + rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+([<>]?=?([0-9]+:)?[a-zA-Z0-9@._+-]+)?$`) magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD} magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A} @@ -71,7 +71,7 @@ type VersionMetadata struct { Conflicts []string `json:"conflicts,omitempty"` Replaces []string `json:"replaces,omitempty"` Backup []string `json:"backup,omitempty"` - Xdata []string `json:"xdata,omitempty"` + XData []string `json:"xdata,omitempty"` } // FileMetadata Metadata related to specific package file. @@ -125,7 +125,7 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) { defer tarball.Close() var pkg *Package - var mtree bool + var mTree bool for { f, err := tarball.Read() @@ -135,24 +135,24 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) { if err != nil { return nil, err } - defer f.Close() - switch f.Name() { case ".PKGINFO": pkg, err = ParsePackageInfo(tarballType, f) if err != nil { + _ = f.Close() return nil, err } case ".MTREE": - mtree = true + mTree = true } + _ = f.Close() } if pkg == nil { return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found") } - if !mtree { + if !mTree { return nil, util.NewInvalidArgumentErrorf(".MTREE file not found") } @@ -220,7 +220,7 @@ func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) { case "replaces": p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value) case "xdata": - p.VersionMetadata.Xdata = append(p.VersionMetadata.Xdata, value) + p.VersionMetadata.XData = append(p.VersionMetadata.XData, value) case "builddate": bd, err := strconv.ParseInt(value, 10, 64) if err != nil { @@ -260,48 +260,43 @@ func ValidatePackageSpec(p *Package) error { return util.NewInvalidArgumentErrorf("invalid project URL") } } - for _, cd := range p.VersionMetadata.CheckDepends { - if !rePkgVer.MatchString(cd) { - return util.NewInvalidArgumentErrorf("invalid check dependency: %s", cd) + for _, checkDepend := range p.VersionMetadata.CheckDepends { + if !rePkgVer.MatchString(checkDepend) { + return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend) } } - for _, d := range p.VersionMetadata.Depends { - if !rePkgVer.MatchString(d) { - return util.NewInvalidArgumentErrorf("invalid dependency: %s", d) + for _, depend := range p.VersionMetadata.Depends { + if !rePkgVer.MatchString(depend) { + return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend) } } - for _, md := range p.VersionMetadata.MakeDepends { - if !rePkgVer.MatchString(md) { - return util.NewInvalidArgumentErrorf("invalid make dependency: %s", md) + for _, makeDepend := range p.VersionMetadata.MakeDepends { + if !rePkgVer.MatchString(makeDepend) { + return util.NewInvalidArgumentErrorf("invalid make dependency: %s", makeDepend) } } - for _, p := range p.VersionMetadata.Provides { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid provides: %s", p) + for _, provide := range p.VersionMetadata.Provides { + if !rePkgVer.MatchString(provide) { + return util.NewInvalidArgumentErrorf("invalid provides: %s", provide) } } - for _, p := range p.VersionMetadata.Conflicts { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid conflicts: %s", p) + for _, conflict := range p.VersionMetadata.Conflicts { + if !rePkgVer.MatchString(conflict) { + return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict) } } - for _, p := range p.VersionMetadata.Replaces { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid replaces: %s", p) + for _, replace := range p.VersionMetadata.Replaces { + if !rePkgVer.MatchString(replace) { + return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace) } } - for _, p := range p.VersionMetadata.Replaces { - if !rePkgVer.MatchString(p) { - return util.NewInvalidArgumentErrorf("invalid xdata: %s", p) + for _, optDepend := range p.VersionMetadata.OptDepends { + if !reOptDep.MatchString(optDepend) { + return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend) } } - for _, od := range p.VersionMetadata.OptDepends { - if !reOptDep.MatchString(od) { - return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", od) - } - } - for _, bf := range p.VersionMetadata.Backup { - if strings.HasPrefix(bf, "/") { + for _, b := range p.VersionMetadata.Backup { + if strings.HasPrefix(b, "/") { return util.NewInvalidArgumentErrorf("backup file contains leading forward slash") } } diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 76a8fd4714..c72f812704 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -175,18 +175,20 @@ func CommonRoutes() *web.Route { arch.PushPackage(ctx) return } else if isDelete { - if groupLen < 2 { + if groupLen < 3 { ctx.Status(http.StatusBadRequest) return } - if groupLen == 2 { + if groupLen == 3 { ctx.SetParams("group", "") ctx.SetParams("package", pathGroups[0]) ctx.SetParams("version", pathGroups[1]) + ctx.SetParams("arch", pathGroups[2]) } else { - ctx.SetParams("group", strings.Join(pathGroups[:groupLen-2], "/")) - ctx.SetParams("package", pathGroups[groupLen-2]) - ctx.SetParams("version", pathGroups[groupLen-1]) + ctx.SetParams("group", strings.Join(pathGroups[:groupLen-3], "/")) + ctx.SetParams("package", pathGroups[groupLen-3]) + ctx.SetParams("version", pathGroups[groupLen-2]) + ctx.SetParams("arch", pathGroups[groupLen-1]) } reqPackageAccess(perm.AccessModeWrite)(ctx) if ctx.Written() { diff --git a/routers/api/packages/arch/arch.go b/routers/api/packages/arch/arch.go index 2d3481a33f..15fcc37c70 100644 --- a/routers/api/packages/arch/arch.go +++ b/routers/api/packages/arch/arch.go @@ -9,12 +9,14 @@ import ( "fmt" "io" "net/http" + "path/filepath" "regexp" "strings" packages_model "code.gitea.io/gitea/models/packages" packages_module "code.gitea.io/gitea/modules/packages" arch_module "code.gitea.io/gitea/modules/packages/arch" + "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/services/context" @@ -25,6 +27,8 @@ import ( var ( archPkgOrSig = regexp.MustCompile(`^.*\.pkg\.tar\.\w+(\.sig)*$`) archDBOrSig = regexp.MustCompile(`^.*.db(\.tar\.gz)*(\.sig)*$`) + + locker = sync.NewExclusivePool() ) func apiError(ctx *context.Context, status int, obj any) { @@ -33,6 +37,14 @@ func apiError(ctx *context.Context, status int, obj any) { }) } +func refreshLocker(ctx *context.Context, group string) func() { + key := fmt.Sprintf("pkg_%d_arch_pkg_%s", ctx.Package.Owner.ID, group) + locker.CheckIn(key) + return func() { + locker.CheckOut(key) + } +} + func GetRepositoryKey(ctx *context.Context) { _, pub, err := arch_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) if err != nil { @@ -48,7 +60,8 @@ func GetRepositoryKey(ctx *context.Context) { func PushPackage(ctx *context.Context) { group := ctx.Params("group") - + releaser := refreshLocker(ctx, group) + defer releaser() upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -154,6 +167,7 @@ func PushPackage(ctx *context.Context) { }) if err != nil { apiError(ctx, http.StatusInternalServerError, err) + return } if err = arch_service.BuildPacmanDB(ctx, ctx.Package.Owner.ID, group, p.FileMetadata.Arch); err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -169,7 +183,7 @@ func GetPackageOrDB(ctx *context.Context) { arch = ctx.Params("arch") ) if archPkgOrSig.MatchString(file) { - pkg, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID) + pkg, u, pf, err := arch_service.GetPackageFile(ctx, group, file, ctx.Package.Owner.ID) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) @@ -178,15 +192,12 @@ func GetPackageOrDB(ctx *context.Context) { } return } - - ctx.ServeContent(pkg, &context.ServeHeaderOptions{ - Filename: file, - }) + helper.ServePackageFile(ctx, pkg, u, pf) return } if archDBOrSig.MatchString(file) { - pkg, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID, + pkg, u, pf, err := arch_service.GetPackageDBFile(ctx, group, arch, ctx.Package.Owner.ID, strings.HasSuffix(file, ".sig")) if err != nil { if errors.Is(err, util.ErrNotExist) { @@ -196,9 +207,7 @@ func GetPackageOrDB(ctx *context.Context) { } return } - ctx.ServeContent(pkg, &context.ServeHeaderOptions{ - Filename: file, - }) + helper.ServePackageFile(ctx, pkg, u, pf) return } @@ -207,10 +216,13 @@ func GetPackageOrDB(ctx *context.Context) { func RemovePackage(ctx *context.Context) { var ( - group = ctx.Params("group") - pkg = ctx.Params("package") - ver = ctx.Params("version") + group = ctx.Params("group") + pkg = ctx.Params("package") + ver = ctx.Params("version") + pkgArch = ctx.Params("arch") ) + releaser := refreshLocker(ctx, group) + defer releaser() pv, err := packages_model.GetVersionByNameAndVersion( ctx, ctx.Package.Owner.ID, packages_model.TypeArch, pkg, ver, ) @@ -229,7 +241,13 @@ func RemovePackage(ctx *context.Context) { } deleted := false for _, file := range files { - if file.CompositeKey == group { + extName := fmt.Sprintf("-%s.pkg.tar%s", pkgArch, filepath.Ext(file.LowerName)) + if strings.HasSuffix(file.LowerName, ".sig") { + extName = fmt.Sprintf("-%s.pkg.tar%s.sig", pkgArch, + filepath.Ext(strings.TrimSuffix(file.LowerName, filepath.Ext(file.LowerName)))) + } + if file.CompositeKey == group && + strings.HasSuffix(file.LowerName, extName) { deleted = true err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.ContextUser, file) if err != nil { @@ -242,6 +260,7 @@ func RemovePackage(ctx *context.Context) { err = arch_service.BuildCustomRepositoryFiles(ctx, ctx.Package.Owner.ID, group) if err != nil { apiError(ctx, http.StatusInternalServerError, err) + return } ctx.Status(http.StatusNoContent) } else { diff --git a/services/packages/arch/repository.go b/services/packages/arch/repository.go index de72467421..58433ab5c2 100644 --- a/services/packages/arch/repository.go +++ b/services/packages/arch/repository.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "net/url" "os" "path/filepath" "sort" @@ -20,6 +21,7 @@ import ( packages_module "code.gitea.io/gitea/modules/packages" arch_module "code.gitea.io/gitea/modules/packages/arch" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" packages_service "code.gitea.io/gitea/services/packages" @@ -28,6 +30,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/packet" ) +var locker = sync.NewExclusivePool() + func GetOrCreateRepositoryVersion(ctx context.Context, ownerID int64) (*packages_model.PackageVersion, error) { return packages_service.GetOrCreateInternalPackageVersion(ctx, ownerID, packages_model.TypeArch, arch_module.RepositoryPackage, arch_module.RepositoryVersion) } @@ -101,6 +105,9 @@ func NewFileSign(ctx context.Context, ownerID int64, input io.Reader) (*packages // BuildPacmanDB Create db signature cache func BuildPacmanDB(ctx context.Context, ownerID int64, group, arch string) error { + key := fmt.Sprintf("pkg_%d_arch_db_%s", ownerID, group) + locker.CheckIn(key) + defer locker.CheckOut(key) pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) if err != nil { return err @@ -173,15 +180,18 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages if err != nil { return nil, err } + defer db.Close() gw := gzip.NewWriter(db) + defer gw.Close() tw := tar.NewWriter(gw) + defer tw.Close() count := 0 for _, pkg := range pkgs { versions, err := packages_model.GetVersionsByPackageName( ctx, ownerID, packages_model.TypeArch, pkg.Name, ) if err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } sort.Slice(versions, func(i, j int) bool { return versions[i].CreatedUnix > versions[j].CreatedUnix @@ -190,7 +200,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages for _, ver := range versions { files, err := packages_model.GetFilesByVersionID(ctx, ver.ID) if err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } var pf *packages_model.PackageFile for _, file := range files { @@ -213,7 +223,7 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyDescription, ) if err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } if len(pps) >= 1 { meta := []byte(pps[0].Value) @@ -223,60 +233,50 @@ func createDB(ctx context.Context, ownerID int64, group, arch string) (*packages Mode: int64(os.ModePerm), } if err = tw.WriteHeader(header); err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } if _, err := tw.Write(meta); err != nil { - return nil, errors.Join(tw.Close(), gw.Close(), db.Close(), err) + return nil, err } count++ break } } } - defer gw.Close() - defer tw.Close() if count == 0 { - return nil, errors.Join(db.Close(), io.EOF) + return nil, io.EOF } return db, nil } // GetPackageFile Get data related to provided filename and distribution, for package files // update download counter. -func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, error) { - pf, err := getPackageFile(ctx, group, file, ownerID) - if err != nil { - return nil, err +func GetPackageFile(ctx context.Context, group, file string, ownerID int64) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { + fileSplit := strings.Split(file, "-") + if len(fileSplit) <= 3 { + return nil, nil, nil, errors.New("invalid file format, need ---.pkg.") } - - filestream, _, _, err := packages_service.GetPackageFileStream(ctx, pf) - return filestream, err -} - -// Ejects parameters required to get package file property from file name. -func getPackageFile(ctx context.Context, group, file string, ownerID int64) (*packages_model.PackageFile, error) { var ( - splt = strings.Split(file, "-") - pkgname = strings.Join(splt[0:len(splt)-3], "-") - vername = splt[len(splt)-3] + "-" + splt[len(splt)-2] + pkgName = strings.Join(fileSplit[0:len(fileSplit)-3], "-") + pkgVer = fileSplit[len(fileSplit)-3] + "-" + fileSplit[len(fileSplit)-2] ) - - version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgname, vername) + version, err := packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeArch, pkgName, pkgVer) if err != nil { - return nil, err + return nil, nil, nil, err } - pkgfile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group) + pkgFile, err := packages_model.GetFileForVersionByName(ctx, version.ID, file, group) if err != nil { - return nil, err + return nil, nil, nil, err } - return pkgfile, nil + + return packages_service.GetPackageFileStream(ctx, pkgFile) } -func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, error) { +func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, signFile bool) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) if err != nil { - return nil, err + return nil, nil, nil, err } fileName := fmt.Sprintf("%s.db", arch) if signFile { @@ -284,10 +284,9 @@ func GetPackageDBFile(ctx context.Context, group, arch string, ownerID int64, si } file, err := packages_model.GetFileForVersionByName(ctx, pv.ID, fileName, group) if err != nil { - return nil, err + return nil, nil, nil, err } - filestream, _, _, err := packages_service.GetPackageFileStream(ctx, file) - return filestream, err + return packages_service.GetPackageFileStream(ctx, file) } // GetOrCreateKeyPair gets or creates the PGP keys used to sign repository metadata files diff --git a/templates/package/content/arch.tmpl b/templates/package/content/arch.tmpl index bcc24b585b..6138b1d698 100644 --- a/templates/package/content/arch.tmpl +++ b/templates/package/content/arch.tmpl @@ -16,11 +16,11 @@ pacman-key --lsign-key '{{$.SignMail}}'

 {{- if gt (len $.Groups) 1 -}}
-# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi"  $.PackageDescriptor.Package.LowerName}}
+# {{ctx.Locale.Tr "packages.arch.pacman.repo.multi" $.PackageDescriptor.Package.LowerName}}
 
 {{end -}}
 {{- $GroupSize := (len .Groups) -}}
-{{-  range $i,$v :=  .Groups -}}
+{{-  range $i,$v := .Groups -}}
 {{- if gt $i 0}}
 {{end -}}{{- if gt $GroupSize 1 -}}
 # {{ctx.Locale.Tr "packages.arch.pacman.repo.multi.item" .}}
diff --git a/templates/package/metadata/arch.tmpl b/templates/package/metadata/arch.tmpl
index 822973eb7d..89001b979c 100644
--- a/templates/package/metadata/arch.tmpl
+++ b/templates/package/metadata/arch.tmpl
@@ -1,4 +1,4 @@
 {{if eq .PackageDescriptor.Package.Type "arch"}}
-	{{range .PackageDescriptor.Metadata.License}}
{{svg "octicon-law" 16 "gt-mr-3"}} {{.}}
{{end}} - {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "mr-3"}} {{ctx.Locale.Tr "packages.details.project_site"}}
{{end}} + {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "tw-mr-2"}} {{ctx.Locale.Tr "packages.details.project_site"}}
{{end}} + {{range .PackageDescriptor.Metadata.License}}
{{svg "octicon-law" 16 "tw-mr-2"}} {{.}}
{{end}} {{end}} diff --git a/tests/integration/api_packages_arch_test.go b/tests/integration/api_packages_arch_test.go index af275de442..2cf0186416 100644 --- a/tests/integration/api_packages_arch_test.go +++ b/tests/integration/api_packages_arch_test.go @@ -14,6 +14,7 @@ import ( "io" "net/http" "strings" + "sync" "testing" "testing/fstest" @@ -258,11 +259,15 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", rootURL+"/base/notfound/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) - req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/x86_64", nil). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) @@ -270,12 +275,22 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA respPkg := MakeRequest(t, req, http.StatusOK) files, err := listTarGzFiles(respPkg.Body.Bytes()) require.NoError(t, err) - require.Len(t, files, 1) // other pkg in L225 + require.Len(t, files, 1) - req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", groupURL+"/x86_64/base.db") + + req = NewRequest(t, "GET", groupURL+"/x86_64/base.db"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestWithBody(t, "DELETE", groupURL+"/test/1.0.0-1/aarch64", nil). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", groupURL+"/aarch64/base.db"). + AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) }) @@ -294,12 +309,33 @@ HMhNSS1IzUsBcpJAPFAwwUXSM0u4BjoaR8EoGAWjgGQAAILFeyQADAAA resp := MakeRequest(t, req, http.StatusOK) require.Equal(t, pkgs[key], resp.Body.Bytes()) - req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1", nil). + req = NewRequestWithBody(t, "DELETE", groupURL+"/test2/1.0.0-1/any", nil). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) }) } } + t.Run("Concurrent Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + var wg sync.WaitGroup + + targets := []string{"any", "aarch64", "x86_64"} + for _, tag := range targets { + wg.Add(1) + go func(i string) { + defer wg.Done() + req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgs[i])). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + }(tag) + } + wg.Wait() + for _, target := range targets { + req := NewRequestWithBody(t, "DELETE", rootURL+"/test/1.0.0-1/"+target, nil). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + } + }) } func getProperty(data, key string) string { @@ -318,10 +354,10 @@ func getProperty(data, key string) string { func listTarGzFiles(data []byte) (fstest.MapFS, error) { reader, err := gzip.NewReader(bytes.NewBuffer(data)) - defer reader.Close() if err != nil { return nil, err } + defer reader.Close() tarRead := tar.NewReader(reader) files := make(fstest.MapFS) for { From 7d45c1c6c779ac73825507dcea27689a08925ba9 Mon Sep 17 00:00:00 2001 From: Codeberg Translate Date: Sat, 28 Sep 2024 09:40:29 +0000 Subject: [PATCH 015/166] i18n: update of translations from Codeberg Translate (#5355) Co-authored-by: earl-warren Co-authored-by: Vaclovas Intas Co-authored-by: Zughy Co-authored-by: aleksi Co-authored-by: Application-Maker Co-authored-by: Salif Mehmed Co-authored-by: 0ko <0ko@users.noreply.translate.codeberg.org> Co-authored-by: Fjuro Co-authored-by: Panagiotis \"Ivory\" Vasilopoulos Co-authored-by: claudep Co-authored-by: vri Co-authored-by: nicokaiser Co-authored-by: Outbreak2096 Co-authored-by: robines Co-authored-by: nazrin Co-authored-by: Kaede Fujisaki Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5355 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Codeberg Translate Co-committed-by: Codeberg Translate (cherry picked from commit e40554f89baa79d12a1ff89b434041b297afff02) --- options/locale/locale_bg.ini | 25 +++- options/locale/locale_cs-CZ.ini | 38 +++-- options/locale/locale_de-DE.ini | 32 ++++- options/locale/locale_el-GR.ini | 242 ++++++++++++++++---------------- options/locale/locale_fi-FI.ini | 5 + options/locale/locale_fr-FR.ini | 58 +++++--- options/locale/locale_is-IS.ini | 8 +- options/locale/locale_it-IT.ini | 23 +++ options/locale/locale_ja-JP.ini | 7 + options/locale/locale_lt.ini | 77 +++++++++- options/locale/locale_nb_NO.ini | 113 ++++++++++++++- options/locale/locale_ru-RU.ini | 16 ++- options/locale/locale_zh-CN.ini | 34 ++++- 13 files changed, 505 insertions(+), 173 deletions(-) diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini index 01fb84c3c6..2207129cfa 100644 --- a/options/locale/locale_bg.ini +++ b/options/locale/locale_bg.ini @@ -168,6 +168,7 @@ versions.view_all = Вижте вŃички dependencies = ЗавиŃимоŃти published_by_in = ĐźŃбликŃван %[1]s от %[3]s в %[5]s published_by = ĐźŃбликŃван %[1]s от %[3]s +generic.download = Đзтеглете пакета от командния ред: [tool] hours = %d чаŃа @@ -1020,7 +1021,7 @@ pulls.title_desc_one = иŃка да Ńлее %[1]d подаване от pulls.showing_specified_commit_range = Показани Ńа Ńамо промените ĐĽĐµĐ¶Đ´Ń %[1]s..%[2]s pulls.merged_title_desc_one = Ńля %[1]d подаване от %[2]s в %[3]s %[4]s pulls.no_merge_access = Не Ńте Ńпълномощени за Ńливане на тази заявка за Ńливане. -activity.navbar.code_frequency = ЧеŃтота на кода +activity.navbar.code_frequency = ЧеŃтота на промените activity.git_stats_pushed_1 = е изтлаŃкал activity.git_stats_push_to_branch = към %s и contributors.contribution_type.commits = Подавания @@ -1183,6 +1184,15 @@ diff.hide_file_tree = Скриване на файловото дърво tag.ahead.target = в %s Ńлед този маркер diff.file_image_width = Широчина activity.unresolved_conv_label = Отворено +invisible_runes_line = `Този ред Ńъдържа невидими Уникод знаци` +code.desc = ДоŃтъп Đ´Đľ програмния код, файловете, подаванията и клоновете. +settings.branches.update_default_branch = Обновяване на Ńтандартния клон +settings.default_branch_desc = Đзберете Ńтандартен клон за хранилището, за заявки за Ńливане и подавания на код: +settings.transfer.button = Прехвърляне на притежанието +settings.transfer.modal.title = Прехвърляне на притежанието +ambiguous_runes_line = `Този ред Ńъдържа двŃŃмиŃлени Уникод знаци` +ambiguous_character = `%[1]c [U+%04[1]X] може да бъде объркан Ń %[2]c [U+%04[2]X]` +invisible_runes_header = `Този файл Ńъдържа невидими Уникод знаци` [modal] confirm = Потвърждаване @@ -1279,6 +1289,7 @@ members.member = УчаŃтник members.private_helper = Да е видим teams.no_desc = Този екип няма опиŃание settings.delete_org_desc = Тази организация ще бъде изтрита перманентно. Продължаване? +open_dashboard = Отваряне на таблото [install] admin_password = Парола @@ -1378,6 +1389,7 @@ followers.title.few = ПоŃледователи followers.title.one = ПоŃледовател following.title.one = Следван following.title.few = Следвани +public_activity.visibility_hint.self_public = ВаŃата дейноŃŃ‚ е видима за вŃички, Ń Đ¸Đ·ĐşĐ»ŃŽŃ‡ĐµĐ˝Đ¸Đµ на взаимодейŃтвията в чаŃтни проŃтранŃтва. КонфигŃриране. [home] filter = ДрŃги филтри @@ -1544,6 +1556,8 @@ push_tag = изтлаŃка маркер %[3]s към %[3]s#%[2]s` reject_pull_request = `предложи промени за %[3]s#%[2]s` compare_branch = Сравняване +compare_commits_general = Сравняване на подавания +compare_commits = Сравнете %d подавания [auth] tab_openid = OpenID @@ -1572,6 +1586,12 @@ tab_signin = Влизане tab_signup = РегиŃтриране password_pwned = Паролата, която Ńте избрали, е в ŃпиŃŃŠĐş Ń ĐľŃ‚ĐşŃ€Đ°Đ´Đ˝Đ°Ń‚Đ¸ пароли, разкрити преди това при ĐżŃблични пробиви на данни. Моля, опитайте отново Ń Ń€Đ°Đ·Đ»Đ¸Ń‡Đ˝Đ° парола. confirmation_mail_sent_prompt = Ново ел. пиŃĐĽĐľ за потвърждение е изпратено Đ´Đľ %s. За да завърŃите процеŃа на региŃтрация, моля, проверете входящата Ńи ĐşŃтия и поŃледвайте предоŃтавената връзка в рамките на Ńледващите %s. Đко адреŃŃŠŃ‚ за ел. поща е неправилен, можете да влезете и да поиŃкате Đ´Ń€Ńго ел. пиŃĐĽĐľ за потвърждение да бъде изпратено на различен адреŃ. +hint_login = Вече имате акаŃнт? Влезте! +hint_register = ĐťŃждаете Ńе от акаŃнт? РегиŃтрирайте Ńе. +sign_up_button = РегиŃтрирайте Ńе. +back_to_sign_in = Назад към Вход +sign_in_openid = Продължаване Ń OpenID +send_reset_mail = Đзпращане на ел. пиŃĐĽĐľ за възŃтановяване [aria] footer.software = ОтноŃно този ŃофтŃер @@ -1582,7 +1602,7 @@ footer = Долен колонтитŃĐ» install = ЛеŃен за инŃталиране lightweight = Лек license = Отворен код -install_desc = ПроŃто Ńтартирайте двоичния файл за ваŃата платформа, използвайте Docker, или го полŃчете пакетирано. +install_desc = ПроŃто Ńтартирайте двоичния файл за ваŃата платформа, използвайте Docker, или го полŃчете пакетиран. app_desc = Безпроблемна Git ŃŃĐ»Ńга ŃŃŠŃ ŃамоŃтоятелен Ń…ĐľŃтинг platform = МеждŃплатформен lightweight_desc = Forgejo има ниŃки минимални изиŃквания и може да работи на икономичен Raspberry Pi. СпеŃтете енергията на ваŃата маŃина! @@ -1670,6 +1690,7 @@ contributors.what = приноŃи recent_commits.what = ŃкороŃни подавания component_loading = Зареждане на %s... component_loading_info = Това може да отнеме извеŃтно време… +code_frequency.what = чеŃтота на промените [projects] type-1.display_name = ĐндивидŃален проект diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 9ca8c6b387..4fa19b80f2 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -842,8 +842,8 @@ add_key=PĹ™idat klĂ­ÄŤ ssh_desc=Tyto veĹ™ejnĂ© klĂ­ÄŤe SSH jsou propojeny s vaším účtem. OdpovĂ­dajĂ­cĂ­ soukromĂ© klĂ­ÄŤe umoĹľnĂ­ plnĂ˝ přístup k vašim repozitářům. KlĂ­ÄŤe SSH, kterĂ© byly ověřeny, mohou bĂ˝t pouĹľity pro ověřenĂ­ Git commitĹŻ podepsanĂ˝ch pĹ™es SSH. principal_desc=Tyto SSH Principal certifikáty jsou pĹ™idruĹľeny k vašemu účtu a umoĹľĹujĂ­ plnĂ˝ přístup do vašich repozitářů. gpg_desc=Tyto veĹ™ejnĂ© klĂ­ÄŤe GPG jsou propojeny s vaším účtem a používajĂ­ se k ověřenĂ­ vašich commitĹŻ. UloĹľte je na bezpeÄŤnĂ© mĂ­sto, jelikoĹľ umoĹľĹujĂ­ podepsat commity vaší identitou. -ssh_helper=PotĹ™ebujete pomoct? PodĂ­vejte se do příruÄŤky GitHubu na to vytvoĹ™enĂ­ vlastnĂ­ch klĂ­ÄŤĹŻ SSH nebo vyĹ™ešte běžnĂ© problĂ©my, se kterĂ˝mi se mĹŻĹľete potkat pĹ™i pouĹľitĂ­ SSH. -gpg_helper=PotĹ™ebujete pomoct? PodĂ­vejte se do příruÄŤky GitHubu o GPG. +ssh_helper=PotĹ™ebujete pomoct? PodĂ­vejte se do příruÄŤky, jak vytvoĹ™it vlastnĂ­ klĂ­ÄŤe SSH nebo vyĹ™ešte běžnĂ© problĂ©my, se kterĂ˝mi se mĹŻĹľete potkat pĹ™i pouĹľitĂ­ SSH. +gpg_helper=PotĹ™ebujete pomoct? PodĂ­vejte se do příruÄŤky o GPG. add_new_key=PĹ™idat klĂ­ÄŤ SSH add_new_gpg_key=PĹ™idat klĂ­ÄŤ GPG key_content_ssh_placeholder=ZaÄŤĂ­ná s „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“ nebo „sk-ssh-ed25519@openssh.com“ @@ -1427,7 +1427,7 @@ commitstatus.failure=Chyba commitstatus.pending=ÄŚekajĂ­cĂ­ commitstatus.success=ĂšspÄ›ch -ext_issues=Přístup k externĂ­m problĂ©mĹŻm +ext_issues=ExternĂ­ problĂ©my ext_issues.desc=Odkaz na externĂ­ systĂ©m problĂ©mĹŻ. projects=Projekty @@ -1608,9 +1608,9 @@ issues.no_content=K dispozici nenĂ­ žádnĂ˝ popis. issues.close=Zavřít problĂ©m issues.comment_pull_merged_at=slouÄŤenĂ˝ commit %[1]s do %[2]s %[3]s issues.comment_manually_pull_merged_at=ruÄŤnÄ› slouÄŤenĂ˝ commit %[1]s do %[2]s %[3]s -issues.close_comment_issue=Okomentovat a zavřít +issues.close_comment_issue=Zavřít s komentářem issues.reopen_issue=Znovu otevřít -issues.reopen_comment_issue=Okomentovat a znovu otevřít +issues.reopen_comment_issue=Znovu otevřít s komentářem issues.create_comment=Okomentovat issues.closed_at=`uzavĹ™el/a tento problĂ©m %[2]s` issues.reopened_at=`znovu otevĹ™el/a tento problĂ©m %[2]s` @@ -1995,7 +1995,7 @@ signing.wont_sign.commitssigned=SlouÄŤenĂ­ nebude podepsáno, protoĹľe všechny signing.wont_sign.approved=SlouÄŤenĂ­ nebude podepsáno, protoĹľe poĹľadavek na nataĹľenĂ­ nenĂ­ schválen. signing.wont_sign.not_signed_in=Nejste pĹ™ihlášeni. -ext_wiki=Přístup k externĂ­ Wiki +ext_wiki=ExternĂ­ wiki ext_wiki.desc=Odkaz do externĂ­ Wiki. wiki=Wiki @@ -2436,7 +2436,7 @@ settings.protect_branch_name_pattern=Vzor jmĂ©na chránÄ›nĂ© vÄ›tve settings.protect_branch_name_pattern_desc=Vzory názvĹŻ chránÄ›nĂ˝ch vÄ›tvĂ­. Pro vzorovou syntaxi viz dokumentace. Příklady: main, release/** settings.protect_patterns=Vzory settings.protect_protected_file_patterns=Vzory chránÄ›nĂ˝ch souborĹŻ (oddÄ›lenĂ© stĹ™ednĂ­kem „;“) -settings.protect_protected_file_patterns_desc=ChránÄ›nĂ© soubory, kterĂ© nemajĂ­ povoleno bĂ˝t mÄ›nÄ›ny přímo, i kdyĹľ uĹľivatel má právo pĹ™idávat, upravovat nebo mazat soubory v tĂ©to vÄ›tvi. VĂ­ce vzorĹŻ lze oddÄ›lit pomocĂ­ stĹ™ednĂ­ku („;“). PodĂ­vejte se na github.com/gobwas/glob dokumentaci pro syntaxi vzoru. Příklady: .drone.yml, /docs/**/*.txt. +settings.protect_protected_file_patterns_desc=ChránÄ›nĂ© soubory, kterĂ© nemajĂ­ povoleno bĂ˝t mÄ›nÄ›ny přímo, i kdyĹľ uĹľivatel má právo pĹ™idávat, upravovat nebo mazat soubory v tĂ©to vÄ›tvi. VĂ­ce vzorĹŻ lze oddÄ›lit pomocĂ­ stĹ™ednĂ­ku („;“). PodĂ­vejte se na dokumentaci %[2]s pro syntaxi vzoru. Příklady: .drone.yml, /docs/**/*.txt. settings.protect_unprotected_file_patterns=Vzory nechránÄ›nĂ˝ch souborĹŻ (oddÄ›lenĂ© stĹ™ednĂ­kem „;“) settings.protect_unprotected_file_patterns_desc=NechránÄ›nĂ© soubory, kterĂ© je moĹľnĂ© mÄ›nit přímo, pokud má uĹľivatel právo zápisu, ÄŤĂ­mĹľ se obejde omezenĂ­ push. VĂ­ce vzorĹŻ lze oddÄ›lit pomocĂ­ stĹ™ednĂ­ku („;“). PodĂ­vejte se na %[2]s dokumentaci pro syntaxi vzoru. Příklady: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Zapnout ochranu @@ -3822,7 +3822,7 @@ management=Správa tajnĂ˝ch klĂ­ÄŤĹŻ [actions] actions=Akce -unit.desc=Spravovat integrovanĂ© pipeliny CI/CD pomocĂ­ funkce Forgejo Actions +unit.desc=Spravovat integrovanĂ© pipeliny CI/CD pomocĂ­ funkce Forgejo Actions. status.unknown=NeznámĂ˝ status.waiting=ÄŚekánĂ­ @@ -3987,4 +3987,24 @@ eib = EiB [translation_meta] -test = diky vsem za pomoc :) \ No newline at end of file +test = diky vsem za pomoc :) + +[repo.permissions] +pulls.write = Zapisovat: ZavĂ­rat žádosti o slouÄŤenĂ­ a spravovat metadata jako štĂ­tky, milnĂ­ky, zpracovatele, data dokonÄŤenĂ­ a závislosti. +packages.write = Zapisovat: ZveĹ™ejĹovat a mazat balĂ­ÄŤky pĹ™ipojenĂ© k repozitáři. +projects.read = ÄŚĂ­st: Přístup k projektovĂ˝m nástÄ›nkám repozitáře. +code.write = Zapisovat: OdesĂ­lat zmÄ›ny do repozitáře, vytvářet vÄ›tve a znaÄŤky. +issues.write = Zapisovat: ZavĂ­rat problĂ©my a spravovat metadata jako štĂ­tky, milnĂ­ky, zpracovatele, data dokonÄŤenĂ­ a závislosti. +pulls.read = ÄŚĂ­st: ÄŚĂ­st a vytvářet žádosti o slouÄŤenĂ­. +releases.read = ÄŚĂ­st: Zobrazovat a stahovat vydánĂ­. +releases.write = Zapisovat: ZveĹ™ejĹovat, upravovat a mazat vydánĂ­ a jejich soubory. +wiki.read = ÄŚĂ­st: ÄŚĂ­st integrovanou wiki a jejĂ­ historii. +wiki.write = Zapisovat: Vytvářet, aktualizovat a mazat stránky v integrovanĂ© wiki. +projects.write = Zapisovat: Vytvářet projekty a sloupce a upravovat je. +packages.read = ÄŚĂ­st: Zobrazovat a stahovat balĂ­ÄŤky pĹ™ipojenĂ© k repozitáři. +actions.read = ÄŚĂ­st: Zobrazovat integrovanĂ© pipeliny CI/CD a jejich protokoly. +actions.write = Zapisovat: RuÄŤnÄ› spouštÄ›t, restartovat, rušit nebo schvalovat ÄŤekajĂ­cĂ­ pipeliny CI/CD. +ext_wiki = Přístup k odkazu v externĂ­ wiki. OprávnÄ›nĂ­ jsou spravována externÄ›. +code.read = ÄŚĂ­st: Přístup a klonovánĂ­ kĂłdu v repozitáři. +issues.read = ÄŚĂ­st: ÄŚĂ­st a vytvářet problĂ©my a komentáře. +ext_issues = Přístup k odkazu v externĂ­m sledovacĂ­m systĂ©mu problĂ©mĹŻ. OprávnÄ›nĂ­ jsou spravována externÄ›. \ No newline at end of file diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 39b3596aa7..b112ec82c3 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1420,7 +1420,7 @@ commitstatus.failure=Fehler commitstatus.pending=Ausstehend commitstatus.success=Erfolg -ext_issues=Zugriff auf Externe Issues +ext_issues=Externe Issues ext_issues.desc=Link zu externem Issuetracker. projects=Projekte @@ -1601,9 +1601,9 @@ issues.no_content=Keine Beschreibung angegeben. issues.close=Issue schlieĂźen issues.comment_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s zusammengefĂĽhrt issues.comment_manually_pull_merged_at=hat Commit %[1]s in %[2]s %[3]s manuell zusammengefĂĽhrt -issues.close_comment_issue=Kommentieren und schlieĂźen +issues.close_comment_issue=Mit Kommentar schlieĂźen issues.reopen_issue=Wieder öffnen -issues.reopen_comment_issue=Kommentieren und wieder öffnen +issues.reopen_comment_issue=Mit Kommentar wieder öffnen issues.create_comment=Kommentieren issues.closed_at=`hat diesen Issue %[2]s geschlossen` issues.reopened_at=`hat dieses Issue %[2]s wieder geöffnet` @@ -1984,7 +1984,7 @@ signing.wont_sign.commitssigned=Der Merge-Commit wird nicht signiert werden, da signing.wont_sign.approved=Der Merge-Commit wird nicht signiert werden, da der Pull-Request nicht genehmigt wurde. signing.wont_sign.not_signed_in=Du bist nicht eingeloggt. -ext_wiki=Zugriff auf externes Wiki +ext_wiki=Externes Wiki ext_wiki.desc=Verweis auf externes Wiki. wiki=Wiki @@ -3792,7 +3792,7 @@ management=Secrets verwalten [actions] actions=Actions -unit.desc=Integrierte CI/CD-Pipelines mit Forgejo-Actions verwalten +unit.desc=Integrierte CI/CD-Pipelines mit Forgejo-Actions verwalten. status.unknown=Unbekannt status.waiting=Wartend @@ -3964,4 +3964,24 @@ eib = EiB [translation_meta] -test = ok \ No newline at end of file +test = ok + +[repo.permissions] +code.write = Schreiben: In das Repository pushen, Branches und Tags erstellen. +code.read = Lesen: Zugriff auf das Repository und Klonen. +issues.read = Lesen: Issues und Kommentare lesen und erstellen. +issues.write = Schreiben: Issues schlieĂźen und Metadaten wie Labels, Meilensteine, Zuweisungen, Fälligkeitsdaten und Abhängigkeiten verwalten. +pulls.read = Lesen: Pull-Requests lesen und erstellen. +releases.read = Lesen: Releases ansehen und herunterladen. +releases.write = Schreiben: Releases und ihre Assets veröffentlichen, bearbeiten und löschen. +wiki.read = Read: Das integrierte Wiki und seine Historie lesen. +wiki.write = Schreiben: Seiten im integrierten Wiki erstellen, aktualisieren und löschen. +projects.read = Lesen: Zugriff auf Projektboards des Repositories. +projects.write = Schreiben: Projekte und Spalten erstellen und bearbeiten. +packages.read = Lesen: Pakete dieses Repositories betrachten und herunterladen. +packages.write = Schreiben: Pakete dieses Repositories veröffentlichen und löschen. +actions.read = Lesen: Integrierte CI/CD-Pipelines und ihre Logs betrachten. +actions.write = Schreiben: Ausstehende CI/CD-Pipelines manuell auslösen, neustarten, abbrechen oder genehmigen. +ext_issues = Zugriff auf den Link zu einem externen Issue-Tracker. Die Berechtigungen werden extern verwaltet. +ext_wiki = Zugriff auf den Link zu einem externen Wiki. Die Berechtigungen werden extern verwaltet. +pulls.write = Schreiben: Pull-Requests schlieĂźen und Metadaten wie Labels, Meilensteine, Zuweisungen, Fälligkeitsdaten und Abhängigkeiten verwalten. \ No newline at end of file diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 55478dacf2..2df8dce95d 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -51,7 +51,7 @@ webauthn_error_empty=ΠĎέπει να ÎżĎÎŻĎετε ένα όνομα για webauthn_error_timeout=Το χĎονικό ĎŚĎιο έφταĎε Ď€Ďιν το κλειδί να διαβαĎτεί. ΠαĎακαλώ ανανεώĎτε τη Ďελίδα και Ď€ĎÎżĎπαθήĎτε ξανά. webauthn_reload=ΑνανέωĎη -repository=ΑποθετήĎιο +repository=Repository organization=ÎźĎγανιĎÎĽĎŚĎ‚ mirror=ΑντίγĎαφο new_repo=Νέο αποθετήĎιο @@ -129,7 +129,7 @@ archived=ΑĎχειοθετήθηκε concept_system_global=Γενικό concept_user_individual=Ατομικό -concept_code_repository=ΑποθετήĎιο +concept_code_repository=Repository concept_user_organization=ÎźĎγανιĎÎĽĎŚĎ‚ show_timestamps=ΕμφάνιĎη χĎονοĎημάνĎεων @@ -160,11 +160,11 @@ invalid_data = Τα δεδομένα δεν είναι έγκυĎα: %v test = ΤεĎĎ„ copy_generic = ΑντιγĎαφή Ďτο Ď€ĎόχειĎÎż error413 = Îχετε εξαντλήĎει τους διαθέĎιμους πόĎους Ďας. -new_repo.link = Νέο αποθετήĎιο +new_repo.link = Νέο repository new_migrate.link = Νέα μεταφοĎά new_org.link = Νέος ÎżĎγανιĎÎĽĎŚĎ‚ new_migrate.title = Νέα μεταφοĎά -new_repo.title = Νέο αποθετήĎιο +new_repo.title = Νέο repository new_org.title = Νέος ÎżĎγανιĎÎĽĎŚĎ‚ [aria] @@ -504,8 +504,8 @@ reset_password.text=ΕφόĎον το αίτημα δημιουĎγήθηκε α register_success=Η εγγĎαφή ολοκληĎώθηκε επιτυχώς -issue_assigned.pull=Îź/Η @%[1]s Ďας έχει αναθέĎει Ďτο pull request %[2]s Ďτο αποθετήĎιο %[3]s. -issue_assigned.issue=Îź/Η @%[1]s Ďας ανέθεĎε το ζήτημα %[2]s Ďτο αποθετήĎιο %[3]s. +issue_assigned.pull=Îź/Η @%[1]s Ďας έχει αναθέĎει Ďτο pull request %[2]s Ďτο repository %[3]s. +issue_assigned.issue=Îź/Η @%[1]s Ďας ανέθεĎε το ζήτημα %[2]s Ďτο repository %[3]s. issue.x_mentioned_you=Îź/Η @%s Ďας ανέφεĎε: issue.action.force_push=Îź/Η %[1]s έκανε force-push το %[2]s από %[3]s Ďε %[4]s. @@ -530,13 +530,13 @@ release.downloads=ΛήĎεις: release.download.zip=Πηγαίος Κώδικας (ZIP) release.download.targz=Πηγαίος Κώδικας (TAR.GZ) -repo.transfer.subject_to=Îź/Η %s θα ήθελε να μεταφέĎει το αποθετήĎιο «%s» Ďε %s -repo.transfer.subject_to_you=Îź/Η %s θα ήθελε να Ďας μεταφέĎει το αποθετήĎιο «%s» +repo.transfer.subject_to=Îź/Η %s θα ήθελε να μεταφέĎει το repository «%s» Ďτο %s +repo.transfer.subject_to_you=Îź/Η %s θα ήθελε να Ďας μεταφέĎει το repository «%s» repo.transfer.to_you=εĎάς repo.transfer.body=Για να αποδεχτείτε ή να αποĎĎÎŻĎετε το αίτημα αυτό, επιĎκεφθείτε το %s ή απλά αγνοήĎτε το. repo.collaborator.added.subject=Îź/Η %s Ďας Ď€ĎĎŚĎθεĎε Ďτο %s ως ĎυνεĎγάτη -repo.collaborator.added.text=ΕίĎτε πλέον ĎυνεĎγάτης Ďτο αποθετήĎιο: +repo.collaborator.added.text=ΕίĎτε πλέον ĎυνεĎγάτης Ďτο repository: team_invite.subject=Îź/Η %[1]s Ďας Ď€ĎÎżĎκάλεĎε να Ďυμμετέχετε Ďτον ÎżĎγανιĎÎĽĎŚ %[2]s team_invite.text_1=Îź/Η %[1]s Ďας Ď€ĎÎżĎκάλεĎε να Ďυμμετέχετε Ďτην ομάδα %[2]s του ÎżĎγανιĎμού %[3]s. @@ -615,10 +615,10 @@ username_change_not_local_user=Δεν επιτĎέπεται Ďτους μη Ď„ username_has_not_been_changed=Το όνομα χĎήĎτη δεν άλλαξε repo_name_been_taken=Το όνομα του αποθετηĎίου χĎηĎιμοποιείται ήδη. repository_force_private=Η επιλογή Μόνο Ιδιωτικά είναι ενεĎγοποιημένη: τα ιδιωτικά αποθετήĎια δεν μποĎούν να δημοĎιευθούν. -repository_files_already_exist=ΑĎχεία υπάĎχουν ήδη για αυτό το αποθετήĎιο. ΕπικοινωνήĎτε με το διαχειĎÎąĎτή του ĎĎ…Ďτήματος. -repository_files_already_exist.adopt=ΑĎχεία υπάĎχουν ήδη για αυτό το αποθετήĎιο και μποĎούν να Υιοθετηθούν μόνο. -repository_files_already_exist.delete=Τα αĎχεία υπάĎχουν ήδη για αυτόν το αποθετήĎιο. ΠĎέπει να τα διαγĎάĎετε. -repository_files_already_exist.adopt_or_delete=Τα αĎχεία υπάĎχουν ήδη για αυτόν το αποθετήĎιο. Είτε υιοθετήĎτε τα είτε διαγĎάĎτε τα. +repository_files_already_exist=ΑĎχεία υπάĎχουν ήδη για αυτό το repository. ΕπικοινωνήĎτε με το διαχειĎÎąĎτή του ĎĎ…Ďτήματος. +repository_files_already_exist.adopt=ΑĎχεία υπάĎχουν ήδη για αυτό το repository και μποĎούν μόνο να υιοθετηθούν. +repository_files_already_exist.delete=Τα αĎχεία υπάĎχουν ήδη για αυτόν το repository. ΠĎέπει να τα διαγĎάĎετε. +repository_files_already_exist.adopt_or_delete=ΥπάĎχουν ήδη τα αĎχεία για αυτό το repository. ΠĎέπει να τα υιοθετήĎετε ή να τα διαγĎάĎετε. visit_rate_limit=Συναντήθηκε το ĎŚĎιο Ďυθμού κατά την απομακĎĎ…Ďμένη Ď€ĎĎŚĎβαĎη. 2fa_auth_required=Απαιτήθηκε ταυτοποίηĎη δύο παĎαγόντων κατά την απομακĎĎ…Ďμένη Ď€ĎĎŚĎβαĎη. org_name_been_taken=Το όνομα του ÎżĎγανιĎμού χĎηĎιμοποιείται ήδη. @@ -925,7 +925,7 @@ access_token_deletion_cancel_action=ΆκυĎÎż access_token_deletion_confirm_action=ΔιαγĎαφή access_token_deletion_desc=Η διαγĎαφή ενός διακĎιτικού θα ανακαλέĎει ÎżĎÎąĎτικά την Ď€ĎĎŚĎβαĎη Ďτο λογαĎιαĎÎĽĎŚ Ďας για εφαĎμογές που το χĎηĎιμοποιούν. Συνέχεια; delete_token_success=Το διακĎιτικό έχει διαγĎαφεί. Οι εφαĎμογές που το χĎηĎιμοποιούν δεν έχουν πλέον Ď€ĎĎŚĎβαĎη Ďτο λογαĎιαĎÎĽĎŚ Ďας. -repo_and_org_access=ΠĎĎŚĎβαĎη Ďτο ΑποθετήĎιο και ÎźĎγανιĎÎĽĎŚ +repo_and_org_access=ΠĎĎŚĎβαĎη Ďτο repository και ÎżĎγανιĎÎĽĎŚ permissions_public_only=ΔημόĎια μόνο permissions_access_all=Όλα (δημόĎια, ιδιωτικά, και πεĎιοĎÎąĎμένα) select_permissions=Επιλογή δικαιωμάτων @@ -1005,7 +1005,7 @@ remove_account_link_success=Îź Ďυνδεδεμένος λογαĎιαĎÎĽĎŚĎ‚ hooks.desc=ΠĎÎżĎθήκη webhooks που θα ενεĎγοποιούνται για όλα τα αποθετήĎια που Ďας ανήκουν. orgs_none=Δεν είĎτε μέλος Ďε κάποιο ÎżĎγανιĎÎĽĎŚ. -repos_none=Δεν Ďας ανήκει κανένα κάποιο αποθετήĎιο. +repos_none=Δεν υπάĎχει κάποιο repository που Ďας ανήκει. delete_account=ΔιαγĎαφή του λογαĎιαĎμού Ďας delete_prompt=Αυτή η ενέĎγεια θα διαγĎάĎει μόνιμα το λογαĎιαĎÎĽĎŚ Ďας. ΔΕΝ ÎΑ ΜΠΟΡΕΙ να επανέλθει. @@ -1047,7 +1047,7 @@ language.localization_project = ΒοηθήĎτε μας να μεταφĎάĎÎż language.description = Από εδώ και Ďτο εξής, αυτή η γλώĎĎα θα χĎηĎιμοποιείται από Ď€Ďοεπιλογή για τον λογαĎιαĎÎĽĎŚ Ďας. [repo] -new_repo_helper=Îνα αποθετήĎιο πεĎιέχει όλα τα αĎχεία έĎγου, ĎυμπεĎιλαμβανομένου του ÎąĎτοĎικού εκδόĎεων. Ήδη φιλοξενείται αλλού; ΜετεγκατάĎταĎη αποθετηĎίου. +new_repo_helper=Îνα repository πεĎιέχει όλα τα αĎχεία έĎγου, ĎυμπεĎιλαμβανομένου του ÎąĎτοĎικού εκδόĎεων. Îχετε ήδη ένα που φιλοξενείται κάπου αλλού; ΜεταφοĎά αποθετηĎίου. owner=Ιδιοκτήτης owner_helper=ÎźĎÎąĎμένοι ÎżĎγανιĎμοί ενδέχεται να μην εμφανίζονται Ďτο αναπτυĎĎόμενο μενού λόγω του μέγιĎτου αĎιθμού αποθετηĎίων. repo_name=Όνομα αποθετηĎίου @@ -1055,7 +1055,7 @@ repo_name_helper=Τα καλά ονόματα αποθετηĎίων χĎηĎÎą repo_size=Μέγεθος ΑποθετηĎίου template=ΠĎότυπο template_select=Επιλέξτε ένα Ď€Ďότυπο -template_helper=ΜετατĎοπή Ďε Ď€Ďότυπο αποθετήĎιο +template_helper=ΜετατĎοπή Ďε Ď€Ďότυπο repository template_description=Τα Ď€Ďότυπα αποθετήĎια επιτĎέπουν Ďτους χĎήĎτες να δημιουĎγήĎουν νέα αποθετήĎια με την ίδια δομή, αĎχεία και Ď€ĎοαιĎετικές ĎυθμίĎεις. visibility=ÎźĎατότητα visibility_description=Μόνο Îż ιδιοκτήτης ή τα μέλη του ÎżĎγανιĎμού εάν έχουν δικαιώματα, θα είναι Ďε θέĎη να το δουν. @@ -1070,7 +1070,7 @@ fork_to_different_account=Fork Ďε διαφοĎετικό λογαĎιαĎÎĽĎŚ fork_visibility_helper=Η ÎżĎατότητα ενός fork αποθετηĎίου δεν μποĎεί να αλλάξει. fork_branch=Κλάδος που θα κλωνοποιηθεί Ďτο fork all_branches=Όλοι οι κλάδοι -fork_no_valid_owners=Αυτό το αποθετήĎιο δεν μποĎεί να γίνει fork επειδή δεν υπάĎχουν έγκυĎοι ιδιοκτήτες. +fork_no_valid_owners=Αυτό το repository δεν μποĎεί να γίνει fork, επειδή δεν υπάĎχουν έγκυĎοι ιδιοκτήτες. use_template=ΧĎήĎη αυτού του Ď€Ďότυπου clone_in_vsc=ΚλωνοποίηĎη Ďτο VS Code download_zip=ΛήĎη ZIP @@ -1120,7 +1120,7 @@ mirror_password_blank_placeholder=(Μη ÎżĎÎąĎμένο) mirror_password_help=Αλλάξτε το όνομα χĎήĎτη για να διαγĎάĎετε έναν αποθηκευμένο κωδικό Ď€ĎĎŚĎβαĎης. watchers=ΠαĎατηĎητές stargazers=ÎαυμαĎτές -stars_remove_warning=Αυτό θα αφαιĎέĎει όλα τα αĎτέĎια από αυτό το αποθετήĎιο. +stars_remove_warning=Αυτό θα αφαιĎέĎει όλα τα αĎτέĎια από αυτό το repository. forks=Forks reactions_more=και %d πεĎÎąĎĎότεĎα unit_disabled=Îź διαχειĎÎąĎτής του ÎąĎτότοπου έχει απενεĎγοποιήĎει αυτήν την ενότητα αποθετηĎίου. @@ -1129,7 +1129,7 @@ adopt_search=ΕιĎάγετε όνομα χĎήĎτη για αναζήτηĎη adopt_preexisting_label=ΥιοθέτηĎη αĎχείων adopt_preexisting=ΥιοθετήĎτε τα Ď€ĎοϋπάĎχοντα αĎχεία adopt_preexisting_content=ΔημιουĎγία αποθετηĎίου από %s -adopt_preexisting_success=Υιοθετήθηκαν αĎχεία και δημιουĎγήθηκε το αποθετήĎιο από %s +adopt_preexisting_success=Υιοθετήθηκαν αĎχεία και δημιουĎγήθηκε το repository από %s delete_preexisting_label=ΔιαγĎαφή delete_preexisting=ΔιαγĎαφή αĎχείων που Ď€ĎοϋπήĎχαν delete_preexisting_content=ΔιαγĎαφή αĎχείων Ďτο %s @@ -1159,17 +1159,17 @@ desc.archived=ΑĎχειοθετημένο template.items=Αντικείμενα Ď€Ďοτύπου template.git_content=ΠεĎιεχόμενο Git (ΠĎοεπιλεγμένος κλάδος) template.git_hooks=Git hooks -template.git_hooks_tooltip=Δεν θα μποĎέĎετε να αφαιĎέĎετε ή να Ď„ĎοποποιήĎετε τα Git hook αφού τα έχετε Ď€ĎÎżĎθέĎει. Επιλέξτε την ĎύθμιĎη αυτή μόνο αν εμπιĎτεύεĎτε το Ď€Ďότυπο αποθετήĎιο. +template.git_hooks_tooltip=Δεν θα μποĎέĎετε να αφαιĎέĎετε ή να Ď„ĎοποποιήĎετε τα Git hook αφού τα έχετε Ď€ĎÎżĎθέĎει. Επιλέξτε την ĎύθμιĎη αυτή μόνο αν εμπιĎτεύεĎτε το Ď€Ďότυπο repository. template.webhooks=Webhooks template.topics=Îέματα template.avatar=Εικόνα template.issue_labels=Ταμπέλες ζητημάτων template.one_item=ΠĎέπει να επιλέξετε τουλάχιĎτον ένα αντικείμενο Ďτο Ď€Ďότυπο -template.invalid=ΠĎέπει να επιλέξετε ένα Ď€Ďότυπο αποθετήĎιο +template.invalid=ΠĎέπει να επιλέξετε ένα Ď€Ďότυπο repository archive.title=Αυτό το αποθετήĎειο αĎχειοθετήθηκε. ΜποĎείτε να Ď€Ďοβάλετε αĎχεία και να τα κλωνοποιήĎετε, αλλά δεν μποĎείτε να ωθήĎετε ή να ανοίξετε ζητήματα ή pull requests. -archive.title_date=Αυτό το αποθετήĎιο έχει αĎχειοθετηθεί Ďτο %s. ΜποĎείτε να Ď€Ďοβάλετε αĎχεία και να κλωνοποιήĎετε, αλλά δεν μποĎείτε να ωθήĎετε ή να ανοίξετε ζητήματα ή pull requests. -archive.issue.nocomment=Αυτό το αποθετήĎιο αĎχειοθετήθηκε. Δεν μποĎείτε να ĎχολιάĎετε Ďε ζητήματα. +archive.title_date=Αυτό το repository αĎχειοθετήθηκε Ďτις %s. ΜποĎείτε να δείτε τα αĎχεία του και να το κλωνοποιήĎετε, αλλά δεν μποĎείτε να κάνετε push, να ανοίξετε ζητήματα ή pull requests. +archive.issue.nocomment=Αυτό το repository έχει αĎχειοθετηθεί. Δεν μποĎείτε να ĎχολιάĎετε Ďε ζητήματα. archive.pull.nocomment=Αυτό το repo αĎχειοθετήθηκε. Δεν μποĎείτε να ĎχολιάĎετε Ďτα pull requests. form.reach_limit_of_creation_1=Îχετε ήδη ĎυμπληĎĎŽĎει το ĎŚĎιο του %d αποθετηĎίου. @@ -1180,7 +1180,7 @@ form.name_pattern_not_allowed=Το μοτίβο «%s» δεν επιτĎέπετ need_auth=ΕξουĎιοδότηĎη migrate_options=ΡυθμίĎεις μεταφοĎάς migrate_service=ΥπηĎεĎία ΜεταφοĎάς -migrate_options_mirror_helper=Αυτό το αποθετήĎιο θα είναι είδωλο +migrate_options_mirror_helper=Αυτό το repository θα είναι είδωλο migrate_options_lfs=ΜεταφοĎά αĎχείων LFS migrate_options_lfs_endpoint.label=ΆκĎÎż LFS migrate_options_lfs_endpoint.description=Η μεταφοĎά θα Ď€ĎÎżĎπαθήĎει να χĎηĎιμοποιήĎει το Git remote για να καθοĎÎŻĎει τον διακομιĎτή LFS. ΜποĎείτε επίĎης να καθοĎÎŻĎετε ένα δικό Ďας endpoint αν τα δεδομένα LFS του αποθετηĎίου αποθηκεύονται κάπου αλλού. @@ -1233,10 +1233,10 @@ migrate.cancel_migrating_confirm=Îέλετε να ακυĎĎŽĎετε αυτή mirror_from=είδωλο του forked_from=forked από generated_from=παĎαγμένο από -fork_from_self=Δεν μποĎείτε να κάνετε fork Ďε ένα αποθετήĎιο που κατέχετε. -fork_guest_user=Συνδεθείτε για να κάνετε fork αυτό το αποθετήĎιο. -watch_guest_user=Συνδεθείτε για να παĎακολουθήĎετε αυτό το αποθετήĎιο. -star_guest_user=Συνδεθείτε για να δώĎετε ένα αĎτέĎÎą Ďε αυτό το αποθετήĎιο. +fork_from_self=Δεν μποĎείτε να κάνετε fork ένα repository που Ďας ανήκει. +fork_guest_user=Συνδεθείτε για να κάνετε fork αυτό το repository. +watch_guest_user=Συνδεθείτε για να παĎακολουθήĎετε αυτό το repository. +star_guest_user=Συνδεθείτε για να δώĎετε ένα αĎτέĎÎą Ďε αυτό το repository. unwatch=ΠαύĎη ακολούθηĎης watch=ΠαĎακολούθηĎη unstar=ΑφαίĎεĎη αĎτεĎιού @@ -1248,11 +1248,11 @@ more_operations=ΠεĎÎąĎĎότεĎες λειτουĎγίες no_desc=ΧωĎÎŻĎ‚ πεĎιγĎαφή quick_guide=ΓĎήγοĎος οδηγός clone_this_repo=ΚλωνοποίηĎη αυτού του αποθετηĎίου -cite_this_repo=ΑναφοĎά Ďε αυτό το αποθετήĎιο +cite_this_repo=ΑναφοĎά Ďε αυτό το repository create_new_repo_command=ΔημιουĎγία νέου αποθετηĎίου Ďτη ÎłĎαμμή εντολών push_exist_repo=ΠĎοώθηĎη ενός υπάĎχοντος αποθετηĎίου από τη ÎłĎαμμή εντολών -empty_message=Αυτό το αποθετήĎιο δεν έχει πεĎιεχόμενο. -broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήĎιο δεν μποĎούν να διαβαĎτούν. ΕπικοινωνήĎτε με το διαχειĎÎąĎτή ή διαγĎάĎτε αυτό το αποθετήĎιο. +empty_message=Αυτό το repository δεν έχει πεĎιεχόμενο. +broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήĎιο δεν μποĎούν να διαβαĎτούν. ΕπικοινωνήĎτε με το διαχειĎÎąĎτή ή διαγĎάĎτε αυτό το repository. code=Κώδικας code.desc=ΠĎĎŚĎβαĎη Ďτον πηγαίο κώδικα, αĎχεία, υποβολές και κλάδους. @@ -1330,7 +1330,7 @@ editor.cannot_edit_non_text_files=Τα δυαδικά αĎχεία δεν μπο editor.edit_this_file=ΕπεξεĎγαĎία αĎχείου editor.this_file_locked=Το αĎχείο είναι κλειδωμένο editor.must_be_on_a_branch=ΠĎέπει να βĎÎŻĎκεĎτε Ďε έναν κλάδο για να κάνετε ή να Ď€Ďοτείνετε αλλαγές Ďε αυτό το αĎχείο. -editor.fork_before_edit=ΠĎέπει να κάνετε fork αυτό το αποθετήĎιο για να κάνετε ή να Ď€Ďοτείνετε αλλαγές Ďε αυτό το αĎχείο. +editor.fork_before_edit=ΠĎέπει να κάνετε fork αυτό το repository για να κάνετε ή να Ď€Ďοτείνετε αλλαγές Ďε αυτό το αĎχείο. editor.delete_this_file=ΔιαγĎαφή αĎχείου editor.must_have_write_access=ΠĎέπει να έχετε Ď€ĎĎŚĎβαĎη εγγĎαφής για να κάνετε ή να Ď€Ďοτείνετε αλλαγές Ďε αυτό το αĎχείο. editor.file_delete_success=Το αĎχείο «%s» έχει διαγĎαφεί. @@ -1359,15 +1359,15 @@ editor.new_branch_name_desc=Όνομα νέου κλάδου… editor.cancel=ΑκύĎωĎη editor.filename_cannot_be_empty=Το όνομα αĎχείου δεν μποĎεί να είναι κενό. editor.filename_is_invalid=Το όνομα αĎχείου δεν είναι έγκυĎÎż: "%s". -editor.branch_does_not_exist=Îź κλάδος "%s" δεν υπάĎχει Ďε αυτό το αποθετήĎιο. -editor.branch_already_exists=Îź κλάδος «%s» υπάĎχει ήδη Ďε αυτό το αποθετήĎιο. -editor.directory_is_a_file=Το όνομα φακέλου «%s» χĎηĎιμοποιείται ήδη ως όνομα αĎχείου Ďε αυτό το αποθετήĎιο. +editor.branch_does_not_exist=Îź κλάδος "%s" δεν υπάĎχει Ďε αυτό το repository. +editor.branch_already_exists=Îź κλάδος «%s» υπάĎχει ήδη Ďε αυτό το repository. +editor.directory_is_a_file=Το όνομα φακέλου «%s» χĎηĎιμοποιείται ήδη ως όνομα αĎχείου Ďε αυτό το repository. editor.file_is_a_symlink=`Το «%s» είναι Ďυμβολικός ĎύνδεĎμος. Οι Ďυμβολικοί ĎύνδεĎμοι δεν μποĎούν να επεξεĎγαĎτούν Ďτην ενĎωματωμένη εφαĎμογή` -editor.filename_is_a_directory=Το όνομα αĎχείου «%s» χĎηĎιμοποιείται ήδη ως όνομα φακέλου Ďε αυτό το αποθετήĎιο. -editor.file_editing_no_longer_exists=Το αĎχείο «%s», το οποίο επεξεĎγάζεĎτε, δεν υπάĎχει πλέον Ďε αυτό το αποθετήĎιο. -editor.file_deleting_no_longer_exists=Το αĎχείο «%s», το οποίο διαγĎάφεται, δεν υπάĎχει πλέον Ďε αυτό το αποθετήĎιο. +editor.filename_is_a_directory=Το όνομα αĎχείου «%s» χĎηĎιμοποιείται ήδη ως όνομα φακέλου Ďε αυτό το repository. +editor.file_editing_no_longer_exists=Το αĎχείο «%s», το οποίο επεξεĎγάζεĎτε, δεν υπάĎχει πλέον Ďε αυτό το repository. +editor.file_deleting_no_longer_exists=Το αĎχείο «%s», το οποίο διαγĎάφεται, δεν υπάĎχει πλέον Ďε αυτό το repository. editor.file_changed_while_editing=ΠĎοέκυĎαν κάποιες αλλαγές Ďτα πεĎιεχόμενα του αĎχείου από τότε που ξεκινήĎατε να τα επεξεĎγάζεĎτε. Κάντε κλικ εδώ για να τα δείτε ή ξανακάντε μία υποβολή των αλλαγών Ďας για να τις αντικαταĎτήĎετε. -editor.file_already_exists=ΥπάĎχει ήδη ένα αĎχείο με το όνομα «%s» Ďε αυτό το αποθετήĎιο. +editor.file_already_exists=ΥπάĎχει ήδη ένα αĎχείο με το όνομα «%s» Ďε αυτό το repository. editor.commit_empty_file_header=Υποβολή ενός κενού αĎχείου editor.commit_empty_file_text=Το αĎχείο που Ď€Ďόκειται να υποβληθεί είναι κενό. Συνέχεια; editor.no_changes_to_show=Δεν υπάĎχουν αλλαγές για εμφάνιĎη. @@ -1620,13 +1620,13 @@ issues.author_helper=Αυτός Îż χĎήĎτης είναι Îż ĎυγγĎαφέ issues.role.owner=Ιδιοκτήτης issues.role.owner_helper=Αυτός Îż χĎήĎτης είναι Îż ιδιοκτήτης αυτού του αποθετηĎίου. issues.role.member=Μέλος -issues.role.member_helper=Αυτός Îż χĎήĎτης είναι μέλος του ÎżĎγανιĎμού, του οποίου ανήκει το αποθετήĎιο. +issues.role.member_helper=Αυτός Îż χĎήĎτης είναι μέλος του ÎżĎγανιĎμού, του οποίου ανήκει το repository. issues.role.collaborator=ΣυνεĎγάτης -issues.role.collaborator_helper=Îź χĎήĎτης έλαβε Ď€ĎĎŚĎκληĎη ĎυνεĎγαĎίας Ďτο αποθετήĎιο. +issues.role.collaborator_helper=Îź χĎήĎτης έλαβε Ď€ĎĎŚĎκληĎη ĎυνεĎγαĎίας Ďτο repository. issues.role.first_time_contributor=ΣυντελεĎτής για Ď€Ďώτη φοĎά -issues.role.first_time_contributor_helper=Αυτή είναι η Ď€Ďώτη ĎυνειĎφοĎά αυτού του χĎήĎτη Ďτο αποθετήĎιο. +issues.role.first_time_contributor_helper=Αυτή είναι η Ď€Ďώτη ĎυνειĎφοĎά αυτού του χĎήĎτη Ďτο repository. issues.role.contributor=ΣυντελεĎτής -issues.role.contributor_helper=Αυτός Îż χĎήĎτης έχει Ď€Ďοηγούμενές υποβολές (commits) Ďτο αποθετήĎιο. +issues.role.contributor_helper=Αυτός Îż χĎήĎτης έχει Ď€Ďοηγούμενές υποβολές (commits) Ďτο repository. issues.re_request_review=ΕπαναίτηĎη αναĎκόπηĎης issues.is_stale=Îχουν υπάĎξει αλλαγές Ďε αυτό το PR από αυτή την αναθεώĎηĎη issues.remove_request_review=ΑφαίĎεĎη αιτήματος αναθεώĎηĎης @@ -1678,7 +1678,7 @@ issues.unlock_comment=: ξεκλείδωĎε αυτή τη Ďυνομιλία %s issues.lock_confirm=Κλείδωμα issues.unlock_confirm=Ξεκλείδωμα issues.lock.notice_1=- Άλλοι χĎήĎτες δεν μποĎούν να αφήĎουν νέα Ďχόλια Ďε αυτό το ζήτημα. -issues.lock.notice_2=- ΕĎείς και άλλοι ĎυνεĎγάτες που έχουν Ď€ĎĎŚĎβαĎη Ďτο αποθετήĎιο θα μποĎείτε ακόμα να αφήĎετε Ďχόλια που θα είναι ÎżĎατά Ďε άλλους. +issues.lock.notice_2=- ΕĎείς, μαζί με τους ĎυνεĎγάτες Ďας που έχουν Ď€ĎĎŚĎβαĎη Ďτο repository, θα μποĎείτε ακόμα να αφήĎετε Ďχόλια που θα μποĎούν να βλέπουν και άλλοι. issues.lock.notice_3=- Îα μποĎείτε να ξεκλειδώĎετε αυτό το ζήτημα πιο μετά. issues.unlock.notice_1=- Όλοι θα βĎÎŻĎκονται πάλι Ďε θέĎη να αφήĎουν Ďχόλιο Ďε αυτό το ζήτημα. issues.unlock.notice_2=- Îα μποĎείτε πάντα να ξανακλειδώĎετε αυτό το ζήτημα πιο μετά. @@ -1759,7 +1759,7 @@ issues.dependency.add_error_dep_issue_not_exist=ΕξαĎτώμενο ζήτημ issues.dependency.add_error_dep_not_exist=Δεν υπάĎχει η ΕξάĎτηĎη. issues.dependency.add_error_dep_exists=Η ΕξάĎτηĎη υπάĎχει ήδη. issues.dependency.add_error_cannot_create_circular=Δεν μποĎείτε να δημιουĎγήĎετε μια εξάĎτηĎη με δύο ζητήματα που μπλοκάĎουν το ένα το άλλο. -issues.dependency.add_error_dep_not_same_repo=Και τα δύο ζητήματα Ď€Ďέπει να βĎÎŻĎκονται Ďτο ίδιο αποθετήĎιο. +issues.dependency.add_error_dep_not_same_repo=Και τα δύο ζητήματα Ď€Ďέπει να βĎÎŻĎκονται Ďτο ίδιο repository. issues.review.self.approval=Δεν μποĎείτε να εγκĎίνετε το δικό Ďας pull request. issues.review.self.rejection=Δεν μποĎείτε να ζητήĎετε αλλαγές Ďτο δικό Ďας pull request. issues.review.approve=ενέκĎινε τις αλλαγές %s @@ -1922,7 +1922,7 @@ pulls.closed_at=`έκλειĎε αυτό το pull request %[2]s` pulls.cmd_instruction_hint=ΠĎοβολή οδηγιών ÎłĎαμμής εντολών pulls.cmd_instruction_checkout_title=Îλεγχος -pulls.cmd_instruction_checkout_desc=Από το αποθετήĎιο του έĎγου Ďας, ελέγξτε έναν νέο κλάδο και δοκιμάĎτε τις αλλαγές. +pulls.cmd_instruction_checkout_desc=Από το repository του έĎγου Ďας, ελέγξτε έναν νέο κλάδο και δοκιμάĎτε τις αλλαγές. pulls.cmd_instruction_merge_title=ΣυγχώνευĎη pulls.cmd_instruction_merge_desc=ΣυγχώνευĎη των αλλαγών και ενημέĎωĎη Ďτο Gitea. pulls.clear_merge_message=ΕκκαθάĎÎąĎη μηνύματος ĎυγχώνευĎης @@ -2101,8 +2101,8 @@ search.code_no_results=Δεν βĎέθηκε πηγαίος κώδικας πο search.code_search_unavailable=Η αναζήτηĎη κώδικα δεν είναι διαθέĎιμη αυτή τη Ďτιγμή. ΠαĎακαλώ επικοινωνήĎτε με το διαχειĎÎąĎτή. settings=ΡυθμίĎεις -settings.desc=Στις ΡυθμίĎεις μποĎείτε να διαχειĎÎąĎτείτε τις ĎυθμίĎεις για το αποθετήĎιο -settings.options=ΑποθετήĎιο +settings.desc=Στις ΡυθμίĎεις μποĎείτε να διαχειĎÎąĎτείτε τις ĎυθμίĎεις για το repository +settings.options=Repository settings.collaboration=ΣυνεĎγάτες settings.collaboration.admin=ΔιαχειĎÎąĎτής settings.collaboration.write=ΕγγĎαφή @@ -2113,18 +2113,18 @@ settings.hooks=Webhooks settings.githooks=Git hooks settings.basic_settings=ΒαĎικές ĎυθμίĎεις settings.mirror_settings=ΡυθμίĎεις ειδώλου -settings.mirror_settings.docs=ΡυθμίĎτε τον αυτόματο ĎυγχĎονιĎÎĽĎŚ των υποβολών, ετικετών και κλάδων του αποθετηĎίου Ďας Ďε ένα άλλο αποθετήĎιο. -settings.mirror_settings.docs.disabled_pull_mirror.instructions=ΡυθμίĎτε τον αυτόματο ĎυγχĎονιĎÎĽĎŚ των υποβολών, ετικετών και κλάδων του έĎγου Ďας με ένα άλλο αποθετήĎιο. Τα είδωλα τύπου λήĎης έχουν απενεĎγοποιηθεί από τον διαχειĎÎąĎτή Ďας. -settings.mirror_settings.docs.disabled_push_mirror.instructions=ΡυθμίĎτε την αυτόματη λήĎη υποβολών, ετικετών και κλάδων από ένα άλλο αποθετήĎιο. +settings.mirror_settings.docs=ΡυθμίĎτε τον αυτόματο ĎυγχĎονιĎÎĽĎŚ των commit, ετικετών και κλάδων του αποθετηĎίου Ďας Ďε ένα άλλο repository. +settings.mirror_settings.docs.disabled_pull_mirror.instructions=ΡυθμίĎτε τον αυτόματο ĎυγχĎονιĎÎĽĎŚ των commit, ετικετών και κλάδων του έĎγου Ďας με ένα άλλο repository. Τα είδωλα τύπου λήĎης έχουν απενεĎγοποιηθεί από τον διαχειĎÎąĎτή Ďας. +settings.mirror_settings.docs.disabled_push_mirror.instructions=ΡυθμίĎτε την αυτόματη λήĎη υποβολών, ετικετών και κλάδων από ένα άλλο repository. settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Αυτή τη Ďτιγμή, αυτό μποĎεί να γίνει μόνο Ďτο μενού "Νέα ΜεταφοĎά". Για πεĎÎąĎĎότεĎες πληĎοφοĎίες, Ďυμβουλευτείτε το: settings.mirror_settings.docs.disabled_push_mirror.info=Τα είδωλα ώθηĎης έχουν απενεĎγοποιηθεί από το διαχειĎÎąĎτή Ďας. -settings.mirror_settings.docs.no_new_mirrors=Το αποθετήĎιο Ďας αντιγĎάφει τις αλλαγές Ď€Ďος ή από ένα άλλο αποθετήĎιο. Λάβετε υπόĎη ότι δεν μποĎείτε να δημιουĎγήĎετε νέα είδωλα αυτή τη Ďτιγμή. +settings.mirror_settings.docs.no_new_mirrors=Το repository Ďας αντιγĎάφει τις αλλαγές Ď€Ďος ή από ένα άλλο repository. Λάβετε υπόĎη ότι δεν μποĎείτε να δημιουĎγήĎετε νέα είδωλα αυτή τη Ďτιγμή. settings.mirror_settings.docs.can_still_use=Αν και δεν μποĎείτε να Ď„ĎοποποιήĎετε τα υπάĎχοντα είδωλα ή να δημιουĎγήĎετε νέα, μποĎείτε να χĎηĎιμοποιείται ακόμα το υπάĎχων είδωλο. settings.mirror_settings.docs.pull_mirror_instructions=Για να ÎżĎÎŻĎετε έναν είδωλο έλξης, παĎακαλούμε Ďυμβουλευθείτε: settings.mirror_settings.docs.more_information_if_disabled=ΜποĎείτε να μάθετε πεĎÎąĎĎότεĎα για τα είδωλα ώθηĎης και έλξης εδώ: settings.mirror_settings.docs.doc_link_title=Πώς μποĎĎŽ να αντιγĎάĎω αποθετήĎια; settings.mirror_settings.docs.doc_link_pull_section=το κεφάλαιο "Pulling from a remote repository" της τεκμηĎίωĎης. -settings.mirror_settings.docs.pulling_remote_title=Îλξη από ένα απομακĎĎ…Ďμένο αποθετήĎιο +settings.mirror_settings.docs.pulling_remote_title=Pull από ένα απομακĎĎ…Ďμένο repository settings.mirror_settings.mirrored_repository=Είδωλο αποθετηĎίου settings.mirror_settings.direction=ΚατεύθυνĎη settings.mirror_settings.direction.pull=Pull @@ -2188,33 +2188,33 @@ settings.reindex_button=ΠĎÎżĎθήκη Ďτην ουĎά Reindex settings.reindex_requested=Αιτήθηκε reindex settings.admin_enable_close_issues_via_commit_in_any_branch=ΚλείĎιμο ενός ζητήματος μέĎω μιας υποβολής που έγινε Ďε έναν μη Ď€Ďοεπιλεγμένο κλάδο settings.danger_zone=Ζώνη κινδύνου -settings.new_owner_has_same_repo=Îź νέος ιδιοκτήτης έχει ήδη ένα αποθετήĎιο με το ίδιο όνομα. ΠαĎακαλώ επιλέξτε ένα άλλο όνομα. -settings.convert=ΜετατĎοπή Ďε κανονικό αποθετήĎιο -settings.convert_desc=ΜποĎείτε να μετατĎέĎετε αυτόν το είδωλο Ďε κανονικό αποθετήĎιο. Αυτό δεν μποĎεί να αναιĎεθεί. -settings.convert_notices_1=Αυτή η λειτουĎγία θα μετατĎέĎει το είδωλο Ďε ένα κανονικό αποθετήĎιο και δεν μποĎεί να αναιĎεθεί. +settings.new_owner_has_same_repo=Îź νέος ιδιοκτήτης έχει ήδη ένα repository με το ίδιο όνομα. ΠαĎακαλώ επιλέξτε ένα άλλο όνομα. +settings.convert=ΜετατĎοπή Ďε κανονικό repository +settings.convert_desc=ΜποĎείτε να μετατĎέĎετε αυτόν το είδωλο Ďε κανονικό repository. Αυτό δεν μποĎεί να αναιĎεθεί. +settings.convert_notices_1=Αυτή η λειτουĎγία θα μετατĎέĎει το είδωλο Ďε ένα κανονικό repository και δεν μποĎεί να αναιĎεθεί. settings.convert_confirm=ΜετατĎοπή αποθετηĎίου -settings.convert_succeed=Το είδωλο έχει μετατĎαπεί Ďε κανονικό αποθετήĎιο. -settings.convert_fork=ΜετατĎοπή Ďε κανονικό αποθετήĎιο -settings.convert_fork_desc=ΜποĎείτε να μετατĎέĎετε αυτό το fork Ďε κανονικό αποθετήĎιο. Αυτό δεν μποĎεί να αναιĎεθεί. -settings.convert_fork_notices_1=Αυτή η λειτουĎγία θα μετατĎέĎει το fork Ďε ένα κανονικό αποθετήĎιο και δεν μποĎεί να αναιĎεθεί. +settings.convert_succeed=Το είδωλο έχει μετατĎαπεί Ďε κανονικό repository. +settings.convert_fork=ΜετατĎοπή Ďε κανονικό repository +settings.convert_fork_desc=ΜποĎείτε να μετατĎέĎετε αυτό το fork Ďε κανονικό repository. Αυτό δεν μποĎεί να αναιĎεθεί. +settings.convert_fork_notices_1=Αυτή η λειτουĎγία θα μετατĎέĎει το fork Ďε ένα κανονικό repository και δεν μποĎεί να αναιĎεθεί. settings.convert_fork_confirm=ΜετατĎοπή αποθετηĎίου -settings.convert_fork_succeed=Το fork έχει μετατĎαπεί Ďε κανονικό αποθετήĎιο. +settings.convert_fork_succeed=Το fork έχει μετατĎαπεί Ďε κανονικό repository. settings.transfer.title=ΜεταβίβαĎη ιδιοκτηĎίας settings.transfer.rejected=Η μεταβίβαĎη του αποθετηĎίου αποĎĎίφθηκε. settings.transfer.success=Η μεταβίβαĎη του αποθετηĎίου ήταν επιτυχής. settings.transfer_abort=ΑκύĎωĎη μεταβίβαĎης settings.transfer_abort_invalid=Δεν μποĎείτε να ακυĎĎŽĎετε μια ανύπαĎκτη μεταβίβαĎη αποθετηĎίου. settings.transfer_abort_success=Η μεταφοĎά αποθετηĎίου Ďτο %s ακυĎώθηκε με επιτυχία. -settings.transfer_desc=ΜεταβιβάĎτε αυτό το αποθετήĎιο Ďε έναν χĎήĎτη ή Ďε έναν ÎżĎγανιĎÎĽĎŚ για τον οποίο έχετε δικαιώματα διαχειĎÎąĎτή. +settings.transfer_desc=ΜεταβιβάĎτε αυτό το repository Ďε έναν χĎήĎτη ή Ďε έναν ÎżĎγανιĎÎĽĎŚ για τον οποίο έχετε δικαιώματα διαχειĎÎąĎτή. settings.transfer_form_title=ΕιĎάγετε το όνομα του αποθετηĎίου ως επιβεβαίωĎη: -settings.transfer_in_progress=Αυτή τη Ďτιγμή υπάĎχει μια εν εξελίξει μεταβίβαĎη. ΠαĎακαλούμε ακυĎĎŽĎτε την αν θέλετε να μεταβιβάĎετε αυτό το αποθετήĎιο Ďε άλλο χĎήĎτη. -settings.transfer_notices_1=- Îα χάĎετε την Ď€ĎĎŚĎβαĎη Ďτο αποθετήĎιο αν το μεταβιβάĎετε Ďε έναν μεμονωμένο χĎήĎτη. -settings.transfer_notices_2=- Îα διατηĎήĎετε την Ď€ĎĎŚĎβαĎη Ďτο αποθετήĎιο αν το μεταβιβάĎετε Ďε έναν ÎżĎγανιĎÎĽĎŚ που είĎτε (Ďυν)ιδιοκτήτης. -settings.transfer_notices_3=- Εάν το αποθετήĎιο είναι ιδιωτικό και μεταβιβάζεται Ďε μεμονωμένο χĎήĎτη, αυτή η ενέĎγεια εξαĎφαλίζει ότι Îż χĎήĎτης έχει τουλάχιĎτον άδεια ανάγνωĎης (και αλλάζει τα δικαιώματα εάν είναι απαĎαίτητο). +settings.transfer_in_progress=Αυτή τη Ďτιγμή υπάĎχει μια εν εξελίξει μεταβίβαĎη. ΠαĎακαλούμε ακυĎĎŽĎτε την αν θέλετε να μεταβιβάĎετε αυτό το repository Ďε άλλο χĎήĎτη. +settings.transfer_notices_1=- Îα χάĎετε την Ď€ĎĎŚĎβαĎη Ďτο repository αν το μεταβιβάĎετε Ďε έναν μεμονωμένο χĎήĎτη. +settings.transfer_notices_2=- Îα διατηĎήĎετε την Ď€ĎĎŚĎβαĎη Ďτο repository αν το μεταβιβάĎετε Ďε έναν ÎżĎγανιĎÎĽĎŚ που είĎτε (Ďυν)ιδιοκτήτης. +settings.transfer_notices_3=- Εάν το repository είναι ιδιωτικό και μεταβιβάζεται Ďε μεμονωμένο χĎήĎτη, αυτή η ενέĎγεια εξαĎφαλίζει ότι Îż χĎήĎτης έχει τουλάχιĎτον άδεια ανάγνωĎης (και αλλάζει τα δικαιώματα εάν είναι απαĎαίτητο). settings.transfer_owner=Νέος ιδιοκτήτης settings.transfer_perform=ΕκτέλεĎη μεταφοĎάς -settings.transfer_started=`Αυτό το αποθετήĎιο έχει επιĎημανθεί για μεταφοĎά και αναμένει επιβεβαίωĎη από το "%s"` -settings.transfer_succeed=Το αποθετήĎιο έχει μεταφεĎθεί. +settings.transfer_started=`Αυτό το repository έχει επιĎημανθεί για μεταφοĎά και αναμένει επιβεβαίωĎη από το "%s"` +settings.transfer_succeed=Το repository έχει μεταφεĎθεί. settings.signing_settings=ΡυθμίĎεις επαλήθευĎης υπογĎαφών settings.trust_model=Μοντέλο εμπιĎτοĎύνης υπογĎαφών settings.trust_model.default=ΠĎοεπιλεγμένο μοντέλο εμπιĎτοĎύνης @@ -2236,33 +2236,33 @@ settings.wiki_deletion_success=Τα δεδομένα wiki του αποθετη settings.delete=ΔιαγĎαφή αυτόυ του αποθετηĎίου settings.delete_desc=Η διαγĎαφή ενός αποθετηĎίου είναι μόνιμη και δεν μποĎεί να αναιĎεθεί. settings.delete_notices_1=- Αυτή η ενέĎγεια ΔΕΝ ΜΠΟΡΕΙ να αναιĎεθεί. -settings.delete_notices_2=- Αυτή η ενέĎγεια θα διαγĎάĎει μόνιμα το αποθετήĎιο %s μαζί με τον κώδικα, τα ζητημάτα, τα Ďχόλια, τα δεδομένα των wiki και τις ĎυθμίĎεις ĎυνεĎγατών που βĎÎŻĎκονται μέĎα Ďε αυτό. +settings.delete_notices_2=- Αυτή η ενέĎγεια θα διαγĎάĎει μόνιμα το repository %s μαζί με τον κώδικα, τα ζητημάτα, τα Ďχόλια, τα δεδομένα των wiki και τις ĎυθμίĎεις ĎυνεĎγατών που βĎÎŻĎκονται μέĎα Ďε αυτό. settings.delete_notices_fork_1=- Τα Forks αυτού του αποθετηĎίου θα γίνουν ανεξάĎτητα μετά τη διαγĎαφή. -settings.deletion_success=Το αποθετήĎιο έχει διαγĎαφεί. +settings.deletion_success=Το repository έχει διαγĎαφεί. settings.update_settings_success=Οι ĎυθμίĎεις του αποθετηĎίου έχουν ενημεĎωθεί. -settings.update_settings_no_unit=Το αποθετήĎιο θα Ď€Ďέπει να επιτĎέπει τουλάχιĎτον κάποιο είδος αλληλεπίδĎαĎης. +settings.update_settings_no_unit=Το repository θα Ď€Ďέπει να επιτĎέπει τουλάχιĎτον κάποιο είδος αλληλεπίδĎαĎης. settings.confirm_delete=ΔιαγĎαφή αποθετηĎίου settings.add_collaborator=ΠĎÎżĎθήκη ĎυνεĎγάτη settings.add_collaborator_success=Îź ĎυνεĎγάτης Ď€ĎÎżĎτέθηκε. settings.add_collaborator_inactive_user=Δεν είναι δυνατή η Ď€ĎÎżĎθήκη ενός ανενεĎγού χĎήĎτη ως ĎυνεĎγάτη. settings.add_collaborator_owner=Δεν είναι δυνατή η Ď€ĎÎżĎθήκη ενός ιδιοκτήτη Ďαν ĎυνεĎγάτη. -settings.add_collaborator_duplicate=Îź ĎυνεĎγάτης έχει ήδη Ď€ĎÎżĎτεθεί Ďε αυτό το αποθετήĎιο. +settings.add_collaborator_duplicate=Îź ĎυνεĎγάτης έχει ήδη Ď€ĎÎżĎτεθεί Ďε αυτό το repository. settings.delete_collaborator=ΚατάĎγηĎη settings.collaborator_deletion=ΚατάĎγηĎη ĎυνεĎγάτη -settings.collaborator_deletion_desc=Η κατάĎγηĎη ενός ĎυνεĎγάτη θα αφαιĎέĎει και την Ď€ĎĎŚĎβαĎή του Ďτο αποθετήĎιο. ΕίĎτε βέβαιοι; +settings.collaborator_deletion_desc=Η κατάĎγηĎη ενός ĎυνεĎγάτη θα αφαιĎέĎει και την Ď€ĎĎŚĎβαĎή του Ďτο repository. ΕίĎτε βέβαιοι; settings.remove_collaborator_success=Îź ĎυνεĎγάτης έχει καταĎγηθεί. settings.search_user_placeholder=ΑναζήτηĎη χĎήĎτη… settings.org_not_allowed_to_be_collaborator=Δεν μποĎείτε να Ď€ĎÎżĎθέĎετε έναν ÎżĎγανιĎÎĽĎŚ ως ĎυνεĎγάτη. -settings.change_team_access_not_allowed=Η αλλαγή της Ď€ĎĎŚĎβαĎης ομάδας για το αποθετήĎιο έχει πεĎιοĎÎąĎτεί Ďτον ιδιοκτήτη του ÎżĎγανιĎμού -settings.team_not_in_organization=Η ομάδα δεν είναι Ďτον ίδιο ÎżĎγανιĎÎĽĎŚ με το αποθετήĎιο +settings.change_team_access_not_allowed=Η αλλαγή της Ď€ĎĎŚĎβαĎης ομάδας για το repository έχει πεĎιοĎÎąĎτεί Ďτον ιδιοκτήτη του ÎżĎγανιĎμού +settings.team_not_in_organization=Η ομάδα δεν είναι Ďτον ίδιο ÎżĎγανιĎÎĽĎŚ με το repository settings.teams=Ομάδες settings.add_team=ΠĎÎżĎθήκη ομάδας -settings.add_team_duplicate=Η ομάδα έχει ήδη το αποθετήĎιο -settings.add_team_success=Η ομάδα έχει πλέον Ď€ĎĎŚĎβαĎη Ďτο αποθετήĎιο. +settings.add_team_duplicate=Η ομάδα έχει ήδη το repository +settings.add_team_success=Η ομάδα έχει πλέον Ď€ĎĎŚĎβαĎη Ďτο repository. settings.search_team=ΑναζήτηĎη Ομάδας… -settings.change_team_permission_tip=Τα δικαιώματα της ομάδας έχουν ÎżĎÎąĎτεί Ďτη Ďελίδα ĎυθμίĎεων της ομάδας και δεν μποĎούν να αλλάξουν ανά αποθετήĎιο +settings.change_team_permission_tip=Τα δικαιώματα της ομάδας έχουν ÎżĎÎąĎτεί Ďτη Ďελίδα ĎυθμίĎεων της ομάδας και δεν μποĎούν να αλλάξουν ανά repository settings.delete_team_tip=Αυτή η ομάδα έχει Ď€ĎĎŚĎβαĎη Ďε όλα τα αποθετήĎια και δεν μποĎεί να αφαιĎεθεί -settings.remove_team_success=Îχει αφαιĎεθεί η Ď€ĎĎŚĎβαĎη της ομάδας Ďτο αποθετήĎιο. +settings.remove_team_success=Îχει αφαιĎεθεί η Ď€ĎĎŚĎβαĎη της ομάδας Ďτο repository. settings.add_webhook=ΠĎÎżĎθήκη webhook settings.add_webhook.invalid_channel_name=Το όνομα του καναλιού Webhook δεν μποĎεί να είναι κενό και δεν μποĎεί να πεĎιέχει μόνο έναν χαĎακτήĎα #. settings.hooks_desc=Τα Webhooks κάνουν αυτόματα αιτήĎεις HTTP POST Ďε ένα διακομιĎτή όταν ενεĎγοποιούνται ÎżĎÎąĎμένα γεγονότα Ďτο Forgejo. ΔιαβάĎτε πεĎÎąĎĎότεĎα Ďτον οδηγό webhooks. @@ -2305,15 +2305,15 @@ settings.event_create_desc=Îź κλάδος ή η ετικέτα δημιουĎÎł settings.event_delete=ΔιαγĎαφή settings.event_delete_desc=Îź κλάδος ή η ετικέτα διαγĎάφηκε. settings.event_fork=Fork -settings.event_fork_desc=Το αποθετήĎιο έγινε fork. +settings.event_fork_desc=Το repository έγινε fork. settings.event_wiki=Wiki settings.event_wiki_desc=Η Ďελίδα Wiki δημιουĎγήθηκε, μετονομάĎτηκε, επεξεĎγάĎτηκε ή διαγĎάφηκε. settings.event_release=ΚυκλοφοĎία settings.event_release_desc=Η έκδοĎη δημοĎιεύτηκε, ενημεĎώθηκε ή διαγĎάφηκε από ένα αποθετήĎιο. settings.event_push=Push -settings.event_push_desc=Git push Ďε ένα αποθετήĎιο. -settings.event_repository=ΑποθετήĎιο -settings.event_repository_desc=Το αποθετήĎιο δημιουĎγήθηκε ή διαγĎάφηκε. +settings.event_push_desc=Git push Ďε ένα repository. +settings.event_repository=Repository +settings.event_repository_desc=Το repository δημιουĎγήθηκε ή διαγĎάφηκε. settings.event_header_issue=Συμβάντα ζητημάτων settings.event_issues=Ζητήματα settings.event_issues_desc=Το ζήτημα άνοιξε, έκλειĎε, ανοίχθηκε εκ νέου ή επεξεĎγάĎτηκε. @@ -2345,7 +2345,7 @@ settings.event_pull_request_review_request_desc=Ζητήθηκε η αξιολό settings.event_pull_request_approvals=ΕγκĎÎŻĎεις pull request settings.event_pull_request_merge=ΣυγχώνευĎη pull request settings.event_package=Πακέτο -settings.event_package_desc=Το πακέτο δημιουĎγήθηκε ή διαγĎάφηκε Ďε ένα αποθετήĎιο. +settings.event_package_desc=Το πακέτο δημιουĎγήθηκε ή διαγĎάφηκε Ďε ένα repository. settings.branch_filter=ΦίλτĎÎż κλάδου settings.branch_filter_desc=ΛίĎτα επιτĎεπόμενων κλάδων για ωθήĎεις, δημιουĎγία κλάδων και γεγονότα διαγĎαφής κλάδων, που ÎżĎίζονται ως μοτίβο glob. Εάν είναι κενό ή *, αναφέĎονται Ďυμβάντα για όλους τους κλάδους. Δείτε τη τεκμηĎίωĎη%[2]s για Ďύνταξη. ΠαĎαδείγματα: master, {master,release*}. settings.authorization_header=Κεφαλίδα authorization @@ -2361,7 +2361,7 @@ settings.hook_type=Είδος hook settings.slack_token=ΔιακĎιτικό settings.slack_domain=Domain settings.slack_channel=Κανάλι -settings.add_web_hook_desc=ΕνĎωμάτωĎε το %s Ďτο αποθετήĎιο Ďας. +settings.add_web_hook_desc=ΕνĎωμάτωĎε το %s Ďτο repository Ďας. settings.web_hook_name_gitea=Gitea settings.web_hook_name_forgejo = Forgejo settings.web_hook_name_gogs=Gogs @@ -2381,9 +2381,9 @@ settings.packagist_api_token=ΔιακĎιτικό API settings.packagist_package_url=URL πακέτων Packagist settings.deploy_keys=Κλειδιά διάθεĎης settings.add_deploy_key=ΠĎÎżĎθήκη κλειδιού διάθεĎης -settings.deploy_key_desc=Τα κλειδιά διάθεĎης έχουν Ď€ĎĎŚĎβαĎη μόνο-ανάγνωĎης Ďτο αποθετήĎιο. +settings.deploy_key_desc=Τα κλειδιά διάθεĎης έχουν Ď€ĎĎŚĎβαĎη μόνο-ανάγνωĎης Ďτο repository. settings.is_writable=ΕνεĎγοποίηĎη Ď€ĎĎŚĎβαĎης εγγĎαφής -settings.is_writable_info=ΕπιτĎέĎτε Ďε αυτό το κλειδί διάθεĎης να ωθήĎει Ďτο αποθετήĎιο. +settings.is_writable_info=ΕπιτĎέĎτε Ďε αυτό το κλειδί διάθεĎης να ωθήĎει Ďτο repository. settings.no_deploy_keys=Δεν υπάĎχουν ακόμα κλειδιά διάθεĎης. settings.title=Τίτλος settings.deploy_key_content=ΠεĎιεχόμενο @@ -2391,7 +2391,7 @@ settings.key_been_used=Îνα κλειδί διάθεĎης με το ίδιο settings.key_name_used=Îνα κλειδί διάθεĎης με το ίδιο όνομα υπάĎχει ήδη. settings.add_key_success=Το κλειδί διάθεĎης «%s» Ď€ĎÎżĎτέθηκε. settings.deploy_key_deletion=ΑφαίĎεĎη κλειδιού διάθεĎης -settings.deploy_key_deletion_desc=Η κατάĎγηĎη ενός κλειδί διάθεĎης θα ανακαλέĎει την Ď€ĎĎŚĎβαĎή του Ďε αυτό το αποθετήĎιο. Συνέχεια; +settings.deploy_key_deletion_desc=Η κατάĎγηĎη ενός κλειδί διάθεĎης θα ανακαλέĎει την Ď€ĎĎŚĎβαĎή του Ďε αυτό το repository. Συνέχεια; settings.deploy_key_deletion_success=Το κλειδί διάθεĎης έχει αφαιĎεθεί. settings.branches=Κλάδοι settings.protected_branch=ΠĎÎżĎταĎία κλάδου @@ -2424,7 +2424,7 @@ settings.protect_check_status_contexts=ΕνεĎγοποίηĎη ελέγχου settings.protect_status_check_patterns=Μοτίβα ελέγχου κατάĎταĎης: settings.protect_status_check_patterns_desc=ÎźĎÎŻĎτε μοτίβα για να καθοĎÎŻĎετε ποιοι έλεγχοι κατάĎταĎης Ď€Ďέπει να πεĎάĎουν Ď€Ďιν οι κλάδοι να μποĎούν να Ďυγχωνευτούν Ďε έναν κλάδο που ταιĎιάζει με αυτόν τον κανόνα. Κάθε ÎłĎαμμή καθοĎίζει ένα μοτίβο. Τα μοτίβα δεν μποĎούν να είναι κενά. settings.protect_check_status_contexts_desc=Απαιτείται έλεγχος κατάĎταĎης για να πεĎάĎει το pull request Ď€Ďιν από τη ĎυγχώνευĎη. Επιλέξτε ποιοι έλεγχοι κατάĎταĎης Ď€Ďέπει να πεĎάĎουν Ď€Ďιν κλάδοι μποĎούν να Ďυγχωνευτούν Ďε έναν κλάδο που ταιĎιάζει με αυτόν τον κανόνα. Όταν είναι ενεĎγοποιημένο, οι υποβολές Ď€Ďέπει Ď€Ďώτα να γίνονται push Ďε άλλο κλάδο, Ďτη Ďυνέχεια, να Ďυγχωνεύονται ή γίνονται push απευθείας Ďε ένα κλάδο που ταιĎιάζει με αυτόν τον κανόνα, αφού έχουν ολοκληĎωθεί οι έλεγχοι κατάĎταĎης. Αν δεν επιλεχθεί κανένα πλαίĎιο, η τελευταία υποβολή Ď€Ďέπει να είναι επιτυχής ανεξάĎτητα από το πλαίĎιο. -settings.protect_check_status_contexts_list=Îλεγχοι κατάĎταĎης που βĎέθηκαν την τελευταία εβδομάδα για αυτό το αποθετήĎιο +settings.protect_check_status_contexts_list=Îλεγχοι κατάĎταĎης που βĎέθηκαν την τελευταία εβδομάδα για αυτό το repository settings.protect_status_check_matched=ΤαιĎιάζει settings.protect_invalid_status_check_pattern=Μη έγκυĎÎż μοτίβο ελέγχου κατάĎταĎης: "%s". settings.protect_no_valid_status_check_patterns=Μη έγκυĎα μοτίβα ελέγχου κατάĎταĎης. @@ -2486,20 +2486,20 @@ settings.matrix.message_type=Είδος μηνύματος settings.archive.button=ΑĎχειοθέτηĎη αποθετηĎίου settings.archive.header=ΑĎχειοθέτηĎη αποθετηĎίου settings.archive.text=Η αĎχειοθέτηĎη του αποθετηĎίου θα το αλλάξει Ďε μόνο για ανάγνωĎη. Δε θα φαίνεται Ďτον αĎχικό πίνακα. Κανείς (ακόμα και εĎείς!) δε θα μποĎεί να κάνει νέες υποβολές, ή να ανοίξει ζητήματα ή pull request. -settings.archive.success=Το αποθετήĎιο αĎχειοθετήθηκε με επιτυχία. +settings.archive.success=Το repository αĎχειοθετήθηκε με επιτυχία. settings.archive.error=ΠαĎουĎιάĎτηκε Ďφάλμα κατά την Ď€ĎÎżĎπάθεια αĎχειοθέτηĎης του αποθετηĎίου. Δείτε το αĎχείο καταγĎαφής για πεĎÎąĎĎότεĎες λεπτομέĎειες. settings.archive.error_ismirror=Δε μποĎείτε να αĎχειοθετήĎετε ένα είδωλο αποθετηĎίου. -settings.archive.branchsettings_unavailable=Οι ĎυθμίĎεις του κλάδου δεν είναι διαθέĎιμες αν το αποθετήĎιο είναι αĎχειοθετημένο. -settings.archive.tagsettings_unavailable=Οι ĎυθμίĎεις της ετικέτας δεν είναι διαθέĎιμες αν το αποθετήĎιο είναι αĎχειοθετημένο. +settings.archive.branchsettings_unavailable=Οι ĎυθμίĎεις του κλάδου δεν είναι διαθέĎιμες αν το repository είναι αĎχειοθετημένο. +settings.archive.tagsettings_unavailable=Οι ĎυθμίĎεις της ετικέτας δεν είναι διαθέĎιμες αν το repository είναι αĎχειοθετημένο. settings.unarchive.button=ΑναίĎεĎη αĎχειοθέτηĎης αποθετηĎίου settings.unarchive.header=Απο-ΑĎχειοθέτηĎη του αποθετηĎίου settings.unarchive.text=Η απο-αĎχειοθέτηĎη του αποθετηĎίου θα αποκαταĎτήĎει την ικανότητά του να λαμβάνει υποβολές και ωθήĎεις, καθώς και νέα ζητήματα και pull-requests. -settings.unarchive.success=Το αποθετήĎιο απο-αĎχειοθετήθηκε με επιτυχία. +settings.unarchive.success=Το repository απο-αĎχειοθετήθηκε με επιτυχία. settings.unarchive.error=ΠαĎουĎιάĎτηκε Ďφάλμα κατά την Ď€ĎÎżĎπάθεια απο-αĎχειοθέτηĎης του αποθετηĎίου. Δείτε τις καταγĎαφές για πεĎÎąĎĎότεĎες λεπτομέĎειες. settings.update_avatar_success=Η εικόνα του αποθετηĎίου έχει ενημεĎωθεί. settings.lfs=LFS -settings.lfs_filelist=ΑĎχεία LFS Ďε αυτό το αποθετήĎιο -settings.lfs_no_lfs_files=Δεν υπάĎχουν αĎχεία LFS Ďε αυτό το αποθετήĎιο +settings.lfs_filelist=ΑĎχεία LFS Ďε αυτό το repository +settings.lfs_no_lfs_files=Δεν υπάĎχουν αĎχεία LFS Ďε αυτό το repository settings.lfs_findcommits=ΕύĎεĎη υποβολών settings.lfs_lfs_file_no_commits=Δεν βĎέθηκαν υποβολές για αυτό το αĎχείο LFS settings.lfs_noattribute=Αυτή η διαδĎομή δεν έχει λειτουĎγία κλειδώματος Ďτον Ď€Ďοεπιλεγμένο κλάδο @@ -2518,7 +2518,7 @@ settings.lfs_force_unlock=ΕξαγκαναĎτικό ξεκλείδωμα settings.lfs_pointers.found=Î’Ďέθηκαν %d δείκτης(ες) blob - %d ĎĎ…ĎχετίĎτηκαν, %d δεν ĎĎ…ĎχετίĎτηκαν (%d λείπουν από το χώĎÎż αποθήκευĎης) settings.lfs_pointers.sha=Blob hash settings.lfs_pointers.oid=OID -settings.lfs_pointers.inRepo=Στο αποθετήĎιο +settings.lfs_pointers.inRepo=Στο repository settings.lfs_pointers.exists=ΥπάĎχει Ďτο χώĎÎż αποθήκευĎης settings.lfs_pointers.accessible=ΠĎÎżĎβάĎιμο Ďτον χĎήĎτη settings.lfs_pointers.associateAccessible=ÎŁĎ…ĎχετιĎÎĽĎŚĎ‚ Ď€ĎÎżĎιτών %d OID @@ -2621,7 +2621,7 @@ release.delete_tag=ΔιαγĎαφή ετικέτας release.deletion=ΔιαγĎαφή κυκλοφοĎίας release.deletion_desc=ΔιαγĎάφοντας μια κυκλοφοĎία, αυτή αφαιĎείται μόνο από το Gitea. Δε θα επηĎεάĎει την ετικέτα Git, τα πεĎιεχόμενα του αποθετηĎίου Ďας ή το ÎąĎτοĎικό της. Συνέχεια; release.deletion_success=Η κυκλοφοĎία έχει διαγĎαφεί. -release.deletion_tag_desc=Îα διαγĎάĎει αυτή την ετικέτα από το αποθετήĎιο. Τα πεĎιεχόμενα του αποθετηĎίου και το ÎąĎτοĎικό παĎαμένουν αμετάβλητα. Συνέχεια; +release.deletion_tag_desc=Îα διαγĎάĎει αυτή την ετικέτα από το repository. Τα πεĎιεχόμενα του repository και το ÎąĎτοĎικό δεν θα πειĎαχτούν. Να γίνει Ďυνέχεια; release.deletion_tag_success=Η ετικέτα έχει διαγĎαφεί. release.tag_name_already_exist=ΥπάĎχει ήδη μια έκδοĎη με αυτό το όνομα ετικέτας. release.tag_name_invalid=Το όνομα της ετικέτας δεν είναι έγκυĎÎż. @@ -2646,7 +2646,7 @@ branch.delete_branch_has_new_commits=Îź κλάδος «%s» δεν μποĎεί branch.create_branch=ΔημιουĎγία κλάδου %s branch.create_from=`από το «%s»` branch.create_success=Îź κλάδος «%s» δημιουĎγήθηκε. -branch.branch_already_exists=Îź κλάδος «%s» υπάĎχει ήδη Ďε αυτό το αποθετήĎιο. +branch.branch_already_exists=Îź κλάδος «%s» υπάĎχει ήδη Ďε αυτό το repository. branch.branch_name_conflict=Το όνομα του κλάδου «%s» ĎυγκĎούεται με το ήδη υπάĎχον κλάδο «%s». branch.tag_collision=Îź κλάδος «%s» δεν μποĎεί να δημιουĎγηθεί επειδή μια ετικέτα με το ίδιο όνομα υπάĎχει ήδη Ďτο αποθετήĎιο. branch.deleted_by=ΔιαγĎάφηκε από %s @@ -2691,7 +2691,7 @@ error.csv.invalid_field_count=Δεν είναι δυνατή η απόδοĎη commits.renamed_from = ΜετονομάĎτηκε από %Ď settings.wiki_rename_branch_main_desc = Îź κλάδος, Îż οποίος χĎηĎιμοποιείται εĎωτεĎικά από το wiki, θα μετονομαĎτεί (μόνιμα και μη αναĎĎ„ĎάĎιμα) Ďε «%s». issues.comment.blocked_by_user = Δεν μποĎείτε να αφήĎετε Ďχόλιο Ďε αυτό το ζήτημα, επειδή Îż κάτοχος του αποθετηĎίου ή το άτομο που δημιούĎγηĎε το ζήτημα Ďας έχει αποκλείĎει. -pulls.blocked_by_user = Δεν μποĎείτε να δημιουĎγήĎετε pull request Ďε αυτό το αποθετήĎιο, επειδή Îż κάτοχος του αποθετηĎίου Ďας έχει αποκλείĎει. +pulls.blocked_by_user = Δεν μποĎείτε να δημιουĎγήĎετε pull request Ďε αυτό το repository, επειδή Îż κάτοχος του repository Ďας έχει αποκλείĎει. pulls.made_using_agit = AGit wiki.cancel = ΑκύĎωĎη settings.units.add_more = ΠĎÎżĎθήκη μονάδων... @@ -2710,13 +2710,13 @@ rss.must_be_on_branch = Για να αποκτήĎετε ένα RSS feed, Ď€Ďέ clone_in_vscodium = ΚλωνοποίηĎη Ďτο VSCodium editor.invalid_commit_mail = Αυτή η διεύθυνĎη email δεν είναι έγκυĎη για την δημιουĎγία μίας υποβολής. pulls.nothing_to_compare_have_tag = Îź επιλεγμένος κλάδος/tag είναι όμοιος. -issues.blocked_by_user = Δεν μποĎείτε να δημιουĎγήĎετε ζητήματα Ďε αυτό το αποθετήĎιο, επειδή Îż κάτοχος του αποθετηĎίου Ďας έχει αποκλείĎει. +issues.blocked_by_user = Δεν μποĎείτε να δημιουĎγήĎετε ζητήματα Ďε αυτό το repository, επειδή Îż κάτοχος του repository Ďας έχει αποκλείĎει. pulls.agit_explanation = ΔημιουĎγημένο μέĎω του AGit. Το AGit επιτĎέπει Ďε ĎυνειĎφέĎοντες να Ď€Ďοτείνουν αλλαγές χĎηĎιμοποιώντας την εντολή «git push», χωĎÎŻĎ‚ την δημιουĎγία fork ή έναν νέο κλάδο. activity.navbar.recent_commits = ΠĎĎŚĎφατες υποβολές settings.wiki_globally_editable = Να επιτĎέπεται η επεξεĎγαĎία του wiki Ďε όλους admin.manage_flags = ΔιαχείĎÎąĎη ĎημάνĎεων -admin.enabled_flags = Το αποθετήĎιο έχει τις εξής ĎημάνĎεις: -settings.mirror_settings.pushed_repository = ΠĎοοĎιζόμενο αποθετήĎιο +admin.enabled_flags = Το repository έχει τις εξής ĎημάνĎεις: +settings.mirror_settings.pushed_repository = ΠĎοοĎιζόμενο repository admin.flags_replaced = Οι ĎημάνĎεις του αποθετηĎίου αντικαταĎτάθηκαν activity.navbar.code_frequency = Συχνότητα κώδικα settings.wiki_branch_rename_success = Το όνομα κλάδου wiki του αποθετηĎίου κανονικοποιήθηκε επιτυχώς. @@ -2767,7 +2767,7 @@ editor.commit_id_not_matching = Το αĎχείο άλλαξε ĎŚĎÎż το επ settings.sourcehut_builds.visibility = ÎźĎατότητα εĎγαĎιών object_format = ΜοĎφή αντικειμένων («object format») settings.ignore_stale_approvals_desc = Οι εγκĎÎŻĎεις, οι οποίες αναφέĎονται Ďε παλαιότεĎες υποβολές, δεν θα Ď€ĎÎżĎμετĎούνται Ďτο Ďύνολο των απαιτούμενων εγκĎÎŻĎεων του pull request. ΕφόĎον αυτές οι εγκĎÎŻĎεις έχουν ήδη ανακληθεί, τότε αυτή η ĎύθμιĎη δεν θα παίξει κάποιον Ďόλο. -settings.archive.mirrors_unavailable = Οι λειτουĎγίες ειδώλου δεν είναι διαθέĎιμες εφόĎον το αποθετήĎιο έχει αĎχειοθετηθεί. +settings.archive.mirrors_unavailable = Οι λειτουĎγίες ειδώλου δεν είναι διαθέĎιμες εφόĎον το repository έχει αĎχειοθετηθεί. settings.web_hook_name_sourcehut_builds = SourceHut Builds settings.enforce_on_admins = ΕφαĎμογή κανόνα Ďε διαχειĎÎąĎτές του αποθετηĎίου object_format_helper = Η ÎĽÎżĎφή αντικειμένων («object format») του αποθετηĎίου. Δεν θα μποĎείτε να το αλλάξετε μεταγενέĎτεĎα. Η πιο Ďυμβατή ÎĽÎżĎφή είναι η SHA1. @@ -2805,7 +2805,7 @@ release.type_attachment = Συνημμένο activity.published_prerelease_label = ΠĎοδημοĎίευĎη activity.published_tag_label = Ετικέτα settings.pull_mirror_sync_quota_exceeded = Îχετε υπεĎβεί τους διαθέĎιμους πόĎους Ďας, για αυτό δεν θα γίνει λήĎη των πιο Ď€ĎĎŚĎφατων αλλαγών. -settings.transfer_quota_exceeded = Îź νέος ιδιοκτήτης (%s) έχει υπεĎβεί τους διαθέĎιμους πόĎους του. Το αποθετήĎιο δεν μποĎεί να μεταφεĎθεί. +settings.transfer_quota_exceeded = Îź νέος ιδιοκτήτης (%s) έχει υπεĎβεί τους διαθέĎιμους πόĎους του. Το repository δεν μποĎεί να μεταφεĎθεί. release.asset_name = Όνομα αĎχείου release.asset_external_url = ΕξωτεĎικό URL release.invalid_external_url = Μη έγκυĎÎż εξωτεĎικό URL: «%s» @@ -2901,7 +2901,7 @@ teams.join=Συμμετοχή teams.leave=ΑποχώĎηĎη teams.leave.detail=ΣίγουĎα θέλετε να αποχωĎήĎετε από την ομάδα %s; teams.can_create_org_repo=ΔημιουĎγία αποθετηĎίων -teams.can_create_org_repo_helper=Τα μέλη μποĎούν να δημιουĎγήĎουν νέα αποθετήĎια Ďτον ÎżĎγανιĎÎĽĎŚ. Îź δημιουĎγός θα αποκτήĎει Ď€ĎĎŚĎβαĎη διαχειĎÎąĎτή Ďτο νέο αποθετήĎιο. +teams.can_create_org_repo_helper=Τα μέλη μποĎούν να δημιουĎγήĎουν νέα αποθετήĎια Ďτον ÎżĎγανιĎÎĽĎŚ. Îź δημιουĎγός θα αποκτήĎει Ď€ĎĎŚĎβαĎη διαχειĎÎąĎτή Ďτο νέο repository. teams.none_access=Καμία Ď€ĎĎŚĎβαĎη teams.none_access_helper=Τα μέλη δεν μποĎούν να δουν ή να κάνουν οποιαδήποτε άλλη ενέĎγεια Ďε αυτή τη μονάδα. teams.general_access=Γενική Ď€ĎĎŚĎβαĎη @@ -2922,7 +2922,7 @@ teams.add_team_member=ΠĎÎżĎθήκη μέλους ομάδας teams.invite_team_member=ΠĎĎŚĎκληĎη Ďτην ομάδα %s teams.invite_team_member.list=ΕκκĎεμείς Ď€ĎÎżĎκλήĎεις teams.delete_team_title=ΔιαγĎαφή ομάδας -teams.delete_team_desc=Η διαγĎαφή μιας ομάδας ανακαλεί τη Ď€ĎĎŚĎβαĎη Ďτο αποθετήĎιο από τα μέλη της. Συνέχεια; +teams.delete_team_desc=Η διαγĎαφή μιας ομάδας ανακαλεί τη Ď€ĎĎŚĎβαĎη Ďτο repository από τα μέλη της. Συνέχεια; teams.delete_team_success=Η ομάδα έχει διαγĎαφεί. teams.read_permission_desc=Αυτή η ομάδα χοĎηγεί Ď€ĎĎŚĎβαĎη ΑνάγνωĎης: τα μέλη μποĎούν να δουν και να κλωνοποιήĎουν τα αποθετήĎια της ομάδας. teams.write_permission_desc=Αυτή η ομάδα χοĎηγεί Ď€ĎĎŚĎβαĎη ΕγγĎαφής: τα μέλη μποĎούν να διαβάĎουν και να κάνουν push Ďτα αποθετήĎια της ομάδας. @@ -2934,9 +2934,9 @@ teams.remove_all_repos_title=ΑφαίĎεĎη όλων των αποθετηĎÎŻ teams.remove_all_repos_desc=Αυτό θα αφαιĎέĎει όλα τα αποθετήĎια από την ομάδα. teams.add_all_repos_title=ΠĎÎżĎθήκη όλων των αποθετηĎίων teams.add_all_repos_desc=Αυτό θα Ď€ĎÎżĎθέĎει όλα τα αποθετήĎια του ÎżĎγανιĎμού Ďτην ομάδα. -teams.add_nonexistent_repo=Το αποθετήĎιο που Ď€ĎÎżĎπαθείτε να Ď€ĎÎżĎθέĎετε δεν υπάĎχει, παĎακαλώ δημιουĎγήĎτε το Ď€Ďώτα. +teams.add_nonexistent_repo=Το repository που Ď€ĎÎżĎπαθείτε να Ď€ĎÎżĎθέĎετε δεν υπάĎχει, παĎακαλώ δημιουĎγήĎτε το Ď€Ďώτα. teams.add_duplicate_users=Îź χĎήĎτης είναι ήδη μέλος της ομάδας. -teams.repos.none=Αυτή η ομάδα δεν έχει Ď€ĎĎŚĎβαĎη Ďε κανένα αποθετήĎιο. +teams.repos.none=Αυτή η ομάδα δεν έχει Ď€ĎĎŚĎβαĎη Ďε κανένα repository. teams.members.none=Δεν υπάĎχουν μέλη Ďε αυτήν την ομάδα. teams.specific_repositories=ΣυγκεκĎιμένα αποθετήĎια teams.specific_repositories_helper=Τα μέλη θα έχουν Ď€ĎĎŚĎβαĎη μόνο Ďε αποθετήĎια που Ď€ĎÎżĎτίθενται Ďητά Ďτην ομάδα. Επιλέγοντας το δεν θα θα αφαιĎεθούν αυτόματα τα αποθετήĎια που έχουν ήδη Ď€ĎÎżĎτεθεί με το Όλα τα αποθετήĎια. @@ -3154,7 +3154,7 @@ packages.creator=ΔημιουĎγός packages.name=Όνομα packages.version=ÎκδοĎη packages.type=Τύπος -packages.repository=ΑποθετήĎιο +packages.repository=Repository packages.size=Μέγεθος packages.published=ΔημοĎιευμένα @@ -3473,7 +3473,7 @@ notices.inverse_selection=ΑντιĎĎ„Ďοφή επιλογής notices.delete_selected=ΔιαγĎαφή επιλεγμένων notices.delete_all=ΔιαγĎαφή όλων των ειδοποιήĎεων notices.type=Τύπος -notices.type_1=ΑποθετήĎιο +notices.type_1=Repository notices.type_2=ΕĎγαĎία notices.desc=ΠεĎιγĎαφή notices.op=Λειτ. @@ -3511,7 +3511,7 @@ users.organization_creation.description = Να επιτĎέπεται η δημ [action] create_repo=δημιούĎγηĎε το αποθετήĎιο %s -rename_repo=μετονόμαĎε το αποθετήĎιο από %[1]s Ďε %[3]s +rename_repo=μετονόμαĎε το repository από %[1]s Ďε %[3]s commit_repo=έκανε push Ďτο %[3]s του %[4]s create_issue=`άνοιξε το ζήτημα %[3]s#%[2]s` close_issue=`έκλειĎε το ζήτημα %[3]s#%[2]s` @@ -3523,7 +3523,7 @@ comment_issue=`άφηĎε Ďχόλιο Ďτο ζήτημα %[3]s comment_pull=`ĎχολίαĎε Ďτο pull request %[3]s#%[2]s` merge_pull_request=`ĎυγχώνευĎε το pull request %[3]s#%[2]s` auto_merge_pull_request=`αυτόματη ĎυγχώνευĎη του pull request %[3]s#%[2]s` -transfer_repo=μετέφεĎε το αποθετήĎιο %s Ďε %s +transfer_repo=μετέφεĎε το repository %s Ďε %s push_tag=ώθηĎε την ετικέτα %[3]s Ďε %[4]s delete_tag=διέγĎαĎε την ετικέτα %[2]s από %[3]s delete_branch=διέγĎαĎε το κλάδο %[2]s από %[3]s @@ -3603,7 +3603,7 @@ title=Πακέτα desc=ΔιαχείĎÎąĎη πακέτων μητĎώου. empty=Δεν υπάĎχουν πακέτα ακόμα. empty.documentation=Για πεĎÎąĎĎότεĎες πληĎοφοĎίες Ďχετικά με το μητĎĎŽÎż πακέτων, Ďυμβουλευτείτε τον οδηγό. -empty.repo=Μήπως ανεβάĎατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε Ďτις ĎυθμίĎεις πακέτων και ĎυνδέĎτε το Ďε αυτό το αποθετήĎιο. +empty.repo=Μήπως ανεβάĎατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε Ďτις ĎυθμίĎεις πακέτων και ĎυνδέĎτε το Ďε αυτό το repository. registry.documentation=Για πεĎÎąĎĎότεĎες πληĎοφοĎίες Ďχετικά με το μητĎĎŽÎż %s, Ďυμβουλευτείτε τον οδηγό. filter.type=Τύπος filter.type.all=Όλα @@ -3644,10 +3644,10 @@ composer.registry=ΡυθμίĎτε αυτό το μητĎĎŽÎż Ďτο αĎχεί composer.install=Για να εγκαταĎτήĎετε το πακέτο χĎηĎιμοποιώντας το Composer, εκτελέĎτε την ακόλουθη εντολή: composer.dependencies=ΕξαĎτήĎεις composer.dependencies.development=ΕξαĎτήĎεις Ανάπτυξης -conan.details.repository=ΑποθετήĎιο +conan.details.repository=Repository conan.registry=ΡυθμίĎτε αυτό το μητĎĎŽÎż από τη ÎłĎαμμή εντολών: conan.install=Για να εγκαταĎτήĎετε το πακέτο χĎηĎιμοποιώντας το Conan, εκτελέĎτε την ακόλουθη εντολή: -conda.registry=ΡυθμίĎτε αυτό το μητĎĎŽÎż ως αποθετήĎιο Conda Ďτο αĎχείο .condarc: +conda.registry=ΡυθμίĎτε αυτό το μητĎĎŽÎż ως repository Conda Ďτο αĎχείο .condarc: conda.install=Για να εγκαταĎτήĎετε το πακέτο χĎηĎιμοποιώντας το Conda, εκτελέĎτε την ακόλουθη εντολή: container.details.type=Τύπος Εικόνας container.details.platform=ΠλατφόĎμα @@ -3705,8 +3705,8 @@ swift.registry=ΡυθμίĎτε αυτό το μητĎĎŽÎż από τη ÎłĎαμ swift.install=ΠĎÎżĎθέĎτε το πακέτο Ďτο αĎχείο Package.swift: swift.install2=και εκτελέĎτε την ακόλουθη εντολή: vagrant.install=Για Ď€ĎÎżĎθήκη ενός κυτίου Vagrant, εκτελέĎτε την ακόλουθη εντολή: -settings.link=ΣύνδεĎη αυτού του πακέτου με ένα αποθετήĎιο -settings.link.description=Εάν ĎυνδέĎετε ένα πακέτο με ένα αποθετήĎιο, το πακέτο πεĎιλαμβάνεται Ďτη λίĎτα πακέτων του αποθετηĎίου. +settings.link=ΣύνδεĎη αυτού του πακέτου με ένα repository +settings.link.description=Εάν ĎυνδέĎετε ένα πακέτο με ένα repository, το πακέτο πεĎιλαμβάνεται Ďτη λίĎτα πακέτων του repository. settings.link.select=Επιλογή ΑποθετηĎίου settings.link.button=ΕνημέĎωĎη ΣυνδέĎμου ΑποθετηĎίου settings.link.success=Îź ĎύνδεĎμος αποθετηĎίου ενημεĎώθηκε επιτυχώς. @@ -3718,7 +3718,7 @@ settings.delete.success=Το πακέτο έχει διαγĎαφεί. settings.delete.error=Αποτυχία διαγĎαφής του πακέτου. owner.settings.cargo.title=ΕυĎετήĎιο μητĎώου Cargo owner.settings.cargo.initialize=ΑĎχικοποίηĎη ευĎετηĎίου -owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό αποθετήĎιο ευĎετηĎίου Git για τη χĎήĎη του μητĎώου Cargo. ΧĎηĎιμοποιώντας αυτή την επιλογή θα δημιουĎγηθεί ξανά το αποθετήĎιο και θα ĎυθμιĎτεί αυτόματα. +owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό repository ευĎετηĎίου Git για τη χĎήĎη του μητĎώου Cargo. ΧĎηĎιμοποιώντας αυτή την επιλογή θα δημιουĎγηθεί ξανά το repository και θα ĎυθμιĎτεί αυτόματα. owner.settings.cargo.initialize.error=Αποτυχία αĎχικοποίηĎης ευĎετηĎίου Cargo: %v owner.settings.cargo.initialize.success=Îź ευĎετήĎιο Cargo δημιουĎγήθηκε με επιτυχία. owner.settings.cargo.rebuild=ΑνανέωĎη ευĎετηĎίου @@ -3811,7 +3811,7 @@ runners.task_list=ΠĎĎŚĎφατες εĎγαĎίες Ďτον εκτελεĎĎ„ runners.task_list.no_tasks=Δεν υπάĎχει καμία εĎγαĎία ακόμα. runners.task_list.run=ΕκτέλεĎη runners.task_list.status=ΚατάĎταĎη -runners.task_list.repository=ΑποθετήĎιο +runners.task_list.repository=Repository runners.task_list.commit=Υποβολή runners.task_list.done_at=ΟλοκλήĎωĎε Στις runners.edit_runner=ΕπεξεĎγαĎία ΕκτελεĎτή diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 643a61b456..75ee89b545 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -147,6 +147,11 @@ value = Arvo rerun = Suorita uudelleen filter.clear = Tyhjennä suodattimet invalid_data = Virheellistä dataa: %v +new_repo.title = Uusi repositorio +new_org.title = Uusi organisaatio +new_org.link = Uusi organisaatio +new_repo.link = Uusi repositorio +new_migrate.link = Uusi siirto [aria] footer.links = Linkit diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 656ead34e1..0df97ec31b 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -207,7 +207,7 @@ string.desc=Z - A [error] occurred=Une erreur s’est produite -report_message=Si vous pensez qu'il s'agit d'un bug Forgejo, veuillez consulter notre board Codeberg ou ouvrir un nouveau ticket si nĂ©cessaire. +report_message=Si vous pensez qu'il s'agit d'un bug Forgejo, veuillez consulter les tickets de Codeberg ou ouvrir un nouveau ticket si nĂ©cessaire. missing_csrf=RequĂŞte incorrecte : aucun jeton CSRF prĂ©sent invalid_csrf=RequĂŞte incorrecte : jeton CSRF invalide not_found=La cible n'a pu ĂŞtre trouvĂ©e. @@ -223,7 +223,7 @@ platform_desc=Forgejo est confirmĂ© fonctionner sur des systèmes d'exploitation lightweight=LĂ©ger lightweight_desc=Forgejo utilise peu de ressources. Il peut mĂŞme tourner sur un Raspberry Pi très bon marchĂ©. Économisez l'Ă©nergie de vos serveurs ! license=Open Source -license_desc=Toutes les sources sont sur Forgejo ! Rejoignez-nous et contribuez Ă  rendre ce projet encore meilleur ! +license_desc=Toutes les sources sont sur Forgejo ! Rejoignez-nous et contribuez Ă  rendre ce projet encore meilleur. Ne craignez pas de devenir un·e contributeur·trice ! [install] install=Installation @@ -558,6 +558,11 @@ removed_security_key.subject = Une clĂ© de sĂ©curitĂ© a Ă©tĂ© supprimĂ©e totp_disabled.subject = TOTP a Ă©tĂ© dĂ©sactivĂ© removed_security_key.no_2fa = Il n'y a plus de mĂ©thodes 2FA configurĂ©es ce qui signifie qu'il n'est plus nĂ©cessaire d'utiliser 2FA pour se connecter Ă  votre compte. account_security_caution.text_1 = Si vous ĂŞtes Ă  l’origine de cette action, vous pouvez ignorer ce courriel. +totp_enrolled.text_1.no_webauthn = Vous venez d'activer TOTP pour votre compte. Cela signifie que pour toutes les prochaines connexions Ă  votre compte, vous devrez utiliser TOTP comme mĂ©thode 2FA. +totp_enrolled.subject = Vous avez activĂ© TOTP comme mĂ©thode 2FA +totp_enrolled.text_1.has_webauthn = Vous venez d'activer TOTP pour votre compte. Cela signifie que pour toutes les prochaines connexions Ă  votre compte, vous pouvez utiliser TOTP comme mĂ©thode 2FA ou l'une de vos clĂ©s de sĂ©curitĂ©. +removed_security_key.text_1 = La clĂ© de sĂ©curitĂ© « %[1]s » vient d'ĂŞtre supprimĂ©e de votre compte. +account_security_caution.text_2 = S'il ne s'agissait pas de vous, votre compte est compromis. Veuillez contacter les administrateurs du site. [modal] yes=Oui @@ -823,7 +828,7 @@ add_new_email=Ajouter une nouvelle adresse e-mail add_new_openid=Ajouter une nouvelle URI OpenID add_email=Ajouter une adresse courriel add_openid=Ajouter une URI OpenID -add_email_confirmation_sent=Un e-mail de confirmation a Ă©tĂ© envoyĂ© Ă  "%s". Veuillez vĂ©rifier votre boĂ®te de rĂ©ception dans les %s suivants pour confirmer votre adresse e-mail. +add_email_confirmation_sent=Un courriel de confirmation a Ă©tĂ© envoyĂ© Ă  « %s ». Pour confirmer votre adresse de courriel, veuillez vĂ©rifier votre boĂ®te de rĂ©ception et suivre le lien indiquĂ© dans les prochains %s. add_email_success=La nouvelle adresse e-mail a Ă©tĂ© ajoutĂ©e. email_preference_set_success=L'e-mail de prĂ©fĂ©rence a Ă©tĂ© dĂ©fini avec succès. add_openid_success=La nouvelle adresse OpenID a Ă©tĂ© ajoutĂ©e. @@ -1042,6 +1047,8 @@ pronouns = Pronoms pronouns_unspecified = Non spĂ©cifiĂ©s language.title = Langue par dĂ©faut keep_activity_private.description = Vous seul pourrez voir votre activitĂ© publique, ainsi que les administrateurs de l'instance. +language.localization_project = Aidez-nous Ă  traduire Forgejo dans votre langue ! En savoir plus. +language.description = Cette langue sera enregistrĂ©e dans votre compte et utilisĂ©e comme langue par dĂ©faut après votre connexion. [repo] new_repo_helper=Un dĂ©pĂ´t contient tous les fichiers d’un projet, ainsi que l’historique de leurs modifications. Vous avez dĂ©jĂ  ça ailleurs ? Migrez-le ici. @@ -1421,7 +1428,7 @@ commitstatus.failure=Échec commitstatus.pending=En attente commitstatus.success=Succès -ext_issues=Accès aux tickets externes +ext_issues=Tickets externes ext_issues.desc=Lien vers un gestionnaire de tickets externe. projects=Projets @@ -1602,9 +1609,9 @@ issues.no_content=Sans contenu. issues.close=Fermer le ticket issues.comment_pull_merged_at=a fusionnĂ© la rĂ©vision %[1]s dans %[2]s %[3]s issues.comment_manually_pull_merged_at=a fusionnĂ© manuellement la rĂ©vision %[1]s dans %[2]s %[3]s -issues.close_comment_issue=Commenter et fermer +issues.close_comment_issue=Fermer avec le commentaire issues.reopen_issue=Rouvrir -issues.reopen_comment_issue=Commenter et rĂ©ouvrir +issues.reopen_comment_issue=RĂ©ouvrir avec le commentaire issues.create_comment=Commenter issues.closed_at=`a fermĂ© ce ticket %[2]s.` issues.reopened_at=`a rĂ©ouvert ce ticket %[2]s.` @@ -1993,7 +2000,7 @@ signing.wont_sign.commitssigned=La fusion ne sera pas signĂ©e car ses rĂ©visions signing.wont_sign.approved=La fusion ne sera pas signĂ©e car la demande d'ajout n'a pas Ă©tĂ© approuvĂ©e. signing.wont_sign.not_signed_in=Vous n'ĂŞtes pas connectĂ©. -ext_wiki=Accès au wiki externe +ext_wiki=Wiki externe ext_wiki.desc=Lier un wiki externe. wiki=Wiki @@ -2418,14 +2425,14 @@ settings.protect_enable_merge_desc=Toute personne ayant un accès en Ă©criture s settings.protect_whitelist_committers=Liste blanche des soumissions (push) settings.protect_whitelist_committers_desc=Seuls les utilisateurs ou les Ă©quipes autorisĂ©s pourront soumettre sur cette branche (sans forcer). settings.protect_whitelist_deploy_keys=Mettez les clĂ©s de dĂ©ploiement sur liste blanche avec accès en Ă©criture pour soumettre. -settings.protect_whitelist_users=Utilisateurs sur liste blanche : +settings.protect_whitelist_users=Utilisateurs sur liste blanche pour pousser settings.protect_whitelist_search_users=Rechercher des utilisateurs… -settings.protect_whitelist_teams=Équipes sur liste blanche : +settings.protect_whitelist_teams=Équipes sur liste blanche pour pousser settings.protect_whitelist_search_teams=Rechercher des Ă©quipes… settings.protect_merge_whitelist_committers=Activer la liste blanche pour la fusion settings.protect_merge_whitelist_committers_desc=N'autoriser que les utilisateurs et les Ă©quipes en liste blanche d'appliquer les demandes de fusion sur cette branche. -settings.protect_merge_whitelist_users=Utilisateurs en liste blanche de fusion : -settings.protect_merge_whitelist_teams=Équipes en liste blanche de fusion : +settings.protect_merge_whitelist_users=Utilisateurs en liste blanche pour fusionner +settings.protect_merge_whitelist_teams=Équipes en liste blanche pour fusionner settings.protect_check_status_contexts=Activer le contrĂ´le de status settings.protect_status_check_patterns=Motifs de vĂ©rification des statuts : settings.protect_status_check_patterns_desc=Entrez des motifs pour spĂ©cifier quelles vĂ©rifications doivent rĂ©ussir avant que des branches puissent ĂŞtre fusionnĂ©es. Un motif par ligne. Un motif ne peut ĂŞtre vide. @@ -2434,12 +2441,12 @@ settings.protect_check_status_contexts_list=ContrĂ´les qualitĂ© trouvĂ©s au cour settings.protect_status_check_matched=Correspondant settings.protect_invalid_status_check_pattern=Motif de vĂ©rification des statuts incorrect : « %s ». settings.protect_no_valid_status_check_patterns=Aucun motif de vĂ©rification des statuts valide. -settings.protect_required_approvals=Minimum d'approbations requis : +settings.protect_required_approvals=Approbations requises settings.protect_required_approvals_desc=Permet de fusionner les demandes d’ajout lorsque suffisamment d’évaluation sont positives. settings.protect_approvals_whitelist_enabled=Restreindre les approbations aux utilisateurs ou aux Ă©quipes en liste blanche settings.protect_approvals_whitelist_enabled_desc=Seuls les Ă©valuations des utilisateurs ou des Ă©quipes suivantes compteront dans les approbations requises. Si laissĂ© vide, les Ă©valuations de toute personne ayant un accès en Ă©criture seront comptabilisĂ©es Ă  la place. -settings.protect_approvals_whitelist_users=Évaluateurs autorisĂ©s : -settings.protect_approvals_whitelist_teams=Équipes d’évaluateurs autorisĂ©s : +settings.protect_approvals_whitelist_users=Évaluateurs autorisĂ©s +settings.protect_approvals_whitelist_teams=Équipes d’évaluateurs autorisĂ©s settings.dismiss_stale_approvals=RĂ©voquer automatiquement les approbations pĂ©rimĂ©es settings.dismiss_stale_approvals_desc=Lorsque des nouvelles rĂ©visions changent le contenu de la demande d’ajout, les approbations existantes sont rĂ©voquĂ©es. settings.ignore_stale_approvals=Ignorer les approbations obsolètes @@ -2449,9 +2456,9 @@ settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche l settings.protect_branch_name_pattern=Motif de nom de branche protĂ©gĂ© settings.protect_branch_name_pattern_desc=Motifs de nom de branche protĂ©gĂ©. Consultez la documentation pour la syntaxe du motif. Exemples : main, release/** settings.protect_patterns=Motifs -settings.protect_protected_file_patterns=Liste des fichiers et motifs protĂ©gĂ©s (sĂ©parĂ©s par un point virgule ";") : -settings.protect_protected_file_patterns_desc=Les fichiers protĂ©gĂ©s ne peuvent ĂŞtre modifiĂ©s, mĂŞme si l'utilisateur a le droit d'ajouter, Ă©diter ou supprimer des fichiers dans cette branche. Plusieurs motifs peuvent ĂŞtre sĂ©parĂ©s par un point-virgule (";"). Veuillez voir github.com/gobwas/glob la documentation pour la syntaxe des motifs. Exemples : .drone.yml, /docs/**/*.txt. -settings.protect_unprotected_file_patterns=Liste des fichiers et motifs exclus (sĂ©parĂ©s par un point virgule ";") : +settings.protect_protected_file_patterns=Motifs de fichiers protĂ©gĂ©s (sĂ©parĂ©s par un point virgule ";") +settings.protect_protected_file_patterns_desc=Les fichiers protĂ©gĂ©s ne peuvent ĂŞtre modifiĂ©s, mĂŞme si l'utilisateur a le droit d'ajouter, Ă©diter ou supprimer des fichiers dans cette branche. Plusieurs motifs peuvent ĂŞtre sĂ©parĂ©s par un point-virgule (";"). Veuillez voir %[2]s la documentation pour la syntaxe des motifs. Exemples : .drone.yml, /docs/**/*.txt. +settings.protect_unprotected_file_patterns=Motifs de fichiers non protĂ©gĂ©s (sĂ©parĂ©s par un point virgule ";") settings.protect_unprotected_file_patterns_desc=Les fichiers non-protĂ©gĂ©s qui peuvent ĂŞtre modifiĂ©s si l'utilisateur a le droit d'Ă©criture, prenant le pas sur les restrictions de push. Plusieurs motifs peuvent ĂŞtre sĂ©parĂ©s par un point-virgule (";"). Veuillez voir %[2]s la documentation pour la syntaxe des motifs. Exemples : .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Activer la protection settings.delete_protected_branch=DĂ©sactiver la protection @@ -2811,6 +2818,23 @@ settings.federation_following_repos = Les URL des dĂ©pĂ´ts suivis sĂ©parĂ©s par settings.federation_not_enabled = La fĂ©dĂ©ration n'est pas activĂ©e pour votre instance. comments.edit.already_changed = Impossible de sauvegarder les changements du commentaire car son contenu a dĂ©jĂ  Ă©tĂ© modifiĂ© par un autre utilisateur. Veuillez recharger la page et essayer de l'Ă©diter Ă  nouveau pour Ă©viter d'Ă©craser ses changements settings.federation_apapiurl = URL de fĂ©dĂ©ration de ce dĂ©pĂ´t. A copier-coller dans les paramètres de fĂ©dĂ©rations d'un autre dĂ©pĂ´t comme URL d'un dĂ©pĂ´t Ă  suivre. +mirror_denied_combination = Il n'est pas possible de combiner une authentification par clĂ© publique et par mot de passe. +mirror_public_key = ClĂ© SSH publique +mirror_use_ssh.text = Utiliser l'authentification SSH +mirror_use_ssh.helper = Forgejo va crĂ©er un miroir du dĂ©pĂ´t via Git sur SSH et crĂ©er une paire de clĂ©s pour vous lorsque vous sĂ©lectionnez cette option. Vous devez vous assurer que la clĂ© publique gĂ©nĂ©rĂ©e est autorisĂ©e Ă  pousser dans le dĂ©pĂ´t de destination. Il n'est pas possible d'utiliser l'autorisation basĂ©e sur un mot de passe si vous choisissez cette option. +no_eol.text = Pas d'EOL +mirror_use_ssh.not_available = L'authentification par SSH n'est pas disponible. +no_eol.tooltip = Ce fichier ne contient pas de caractère final de fin de ligne. +release.type_attachment = Pièce jointe +settings.transfer_quota_exceeded = Le nouvel utilisateur (%s) a dĂ©passĂ© son quota. Le dĂ©pĂ´t n'a pas Ă©tĂ© transfĂ©rĂ©. +settings.pull_mirror_sync_quota_exceeded = Quota dĂ©passĂ©, les modifications ne sont pas tirĂ©es. +activity.commit = ActivitĂ© de commit +settings.mirror_settings.push_mirror.copy_public_key = Copier la clĂ© publique +release.asset_external_url = URL externe +release.invalid_external_url = URL externe non valable : « %s » +milestones.filter_sort.name = Nom +settings.mirror_settings.push_mirror.none_ssh = Aucun +settings.protect_new_rule = CrĂ©er une nouvelle règle de protection de branche [graphs] component_loading=Chargement de %s… diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index e34da348a5..0cb6c0f7ab 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -3,7 +3,7 @@ home=Forsíða dashboard=StjĂłrnborð explore=Vafra help=Hjálp -sign_in=Skrá Inn +sign_in=Skrá inn sign_in_or=eða sign_out=Skrá Ăšt sign_up=NĂ˝skráning @@ -15,9 +15,9 @@ page=Síða template=Sniðmát language=Tungumál notifications=Tilkynningar -active_stopwatch=Virk TĂ­mamæling +active_stopwatch=Virk tĂ­mamæling create_new=Skapa… -user_profile_and_more=Notandasíða og Stillingar… +user_profile_and_more=Notandasíða og stillingar… signed_in_as=Skráð(ur) inn sem toc=Efnisyfirlit licenses=HugbĂşnaðarleyfi @@ -111,6 +111,8 @@ concept_code_repository=HugbĂşnaðarsafn name=Heiti value=Gildi +sign_in_with_provider = Skrá inn með %s +enable_javascript = Ăžessi síða krefst JavaScript. [aria] diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 0860176b18..f6fcd5135f 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -471,6 +471,11 @@ openid_signin_desc = Inserisci il tuo URI OpenID. Per esempio: alice.openid.exam password_pwned = La password che hai scelto è in un elenco di password rubate precedentemente esposte a violazioni di dati pubblici. Riprova con una password diversa e valuta di modificare questa password anche altrove. tab_signup = Registrati tab_signin = Accedi +back_to_sign_in = Torna alla schermata d'accesso +sign_in_openid = Procedi con OpenID +hint_login = Hai giĂ  un'utenza? Accedi! +hint_register = Non hai un'utenza? Registrati ora. +sign_up_button = Registrati ora. [mail] view_it_on=Visualizza su %s @@ -539,6 +544,21 @@ activate_email.title = %s, verifica il tuo indirizzo email admin.new_user.text = Clicca qui per gestire questo utente dal pannello di amministrazione. team_invite.text_1 = %[1]s ti ha invitato a far parte del team %[2]s nell'organizzazione %[3]s. team_invite.text_3 = Nota: Questo invito è destinato a %[1]s. Se non ti aspettavi questo invito, puoi ignorare questa email. +primary_mail_change.subject = La tua mail principale è stata cambiata +removed_security_key.no_2fa = Non ci sono piĂą altri metodi di autenticazione a due fattori configurati, ergo non c'è piĂą bisogno di accedere alla tua utenza tramite tale autenticazione. +primary_mail_change.text_1 = La mail principale della tua utenza è appena stata cambiata in %[1]s. Ciò significa che questo indirizzo di posta elettronica non riceverĂ  piĂą notifiche mail da quest'utenza. +totp_disabled.subject = La TOTP è stata disabilitata +totp_disabled.no_2fa = Non ci sono piĂą altri metodi d'autenticazione a due fattori configurati, ergo non c'è piĂą bisogno di accedere alla tua utenza con tale autenticazione. +removed_security_key.subject = Ă stata rimossa una chiave di sicurezza +removed_security_key.text_1 = La chiave di sicurezza "%[1]s" è appena stata rimossa dalla tua utenza. +totp_disabled.text_1 = La password a tempo usa e getta (TOTP) della tua utenza è appena stata disabilitata. +totp_enrolled.subject = Hai attivato la TOTP come metodo d'autenticazione a due fattori +totp_enrolled.text_1.no_webauthn = Hai appena attivato la TOTP per la tua utenza. Ciò significa che dovrai usarla come metodo d'autenticazione a due fattori per tutti i tuoi accessi futuri. +totp_enrolled.text_1.has_webauthn = Hai appena attivato la TOTP per la tua utenza. Ciò significa che dovrai usare come metodo d'autenticazione a due fattori per i tuoi accessi futuri tale TOTP o una delle tue chiavi di sicurezza. +password_change.subject = La tua password è stata modificata +password_change.text_1 = La password della tua utenza è appena stata modificata. +account_security_caution.text_1 = Se sei statÉ™ tu, puoi ignorare questa mail. +account_security_caution.text_2 = Se non sei statÉ™ tu, la tua utenza è compromessa. Contatta l'amministrazione del sito. [modal] @@ -1022,6 +1042,8 @@ pronouns = Pronomi pronouns_custom = Personalizzato pronouns_unspecified = Non specificato language.title = Lingua predefinita +language.description = Questa lingua verrĂ  salvata nella tua utenza e verrĂ  usata come predefinita ogni volta che farai l'accesso. +language.localization_project = Aiutaci a tradurre Forgejo nella tua lingua! PiĂą informazioni. [repo] owner=Proprietario @@ -3863,6 +3885,7 @@ exact_tooltip = Includi solo i risultati che corrispondono esattamente al termin issue_kind = Cerca segnalazioni... pull_kind = Cerca richieste... exact = Esatto +milestone_kind = Ricerca tappe... [munits.data] gib = GiB diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index f53356ee71..fba7f37808 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -158,6 +158,13 @@ filter.not_template = ă†ăłă—ă¬ăĽăă§ăŻăŞă„ invalid_data = 無効ăŞă‡ăĽă‚ż: %v more_items = ă•らă«čˇ¨ç¤ş copy_generic = クăŞăă—ăśăĽă‰ă¸ă‚łă”㼠+new_repo.title = ć–°ă—ă„ăŞăťă‚¸ă㪠+new_migrate.title = ć–°ă—ă„ăžă‚¤ă‚°ă¬ăĽă‚·ă§ăł +new_org.title = ć–°ă—ă„組織 +new_repo.link = ć–°ă—ă„ăŞăťă‚¸ă㪠+new_migrate.link = ć–°ă—ă„ăžă‚¤ă‚°ă¬ăĽă‚·ă§ăł +new_org.link = ć–°ă—ă„組織 +test = ă†ă‚ąă [aria] navbar=ăŠă“ゲăĽă‚·ă§ăłă㼠diff --git a/options/locale/locale_lt.ini b/options/locale/locale_lt.ini index 35f56fa4fd..b55a749cb2 100644 --- a/options/locale/locale_lt.ini +++ b/options/locale/locale_lt.ini @@ -159,6 +159,18 @@ fuzzy_tooltip = Ä®traukti rezultatus, kurie taip pat labai atitinka paieškos te repo_kind = Ieškoti saugyklĹł... code_search_unavailable = KodĹł paieška šiuo metu nepasiekiama. Kreipkis ÄŻ svetainÄ—s administratoriĹł. org_kind = Ieškoti organizacijĹł... +union = Bendrinis +code_search_by_git_grep = Dabartiniai kodo paieškos rezultatai pateikiami atliekant „git grep“. Rezultatai gali bĹ«ti geresni, jei svetainÄ—s administratorius ÄŻjungs kodo indeksuotojÄ…. +package_kind = Ieškoti paketĹł... +project_kind = Ieškoti projektĹł... +commit_kind = Ieškoti ÄŻsipareigojimĹł... +runner_kind = Ieškoti vykdykliĹł... +no_results = Nerasta atitinkamĹł rezultatĹł. +issue_kind = Ieškoti problemĹł... +branch_kind = Ieškoti šakĹł... +milestone_kind = Ieškoti gairiĹł... +pull_kind = Ieškoti sujungimĹł... +keyword_search_unavailable = Ieškoti pagal raktaĹľodÄŻ šiuo metu nepasiekiamas. Susisiekite su svetainÄ—s administratoriumi. [actions] workflow.disable = Išjungti darbo eigÄ… @@ -171,6 +183,9 @@ runs.empty_commit_message = (tuščias ÄŻsipareigojimo pranešimas) submodule = Pomodulis changed_filemode = %[1]s → %[2]s symbolic_link = Virtualusis aplankas +directory = Katalogas +executable_file = Vykdomasis failas +normal_file = Ä®prastas failas [projects] deleted.display_name = Ištrintas projektas @@ -182,4 +197,64 @@ type-3.display_name = Organizacijos projektas filepreview.truncated = PerĹľiĹ«ra buvo sutrumpinta [mail] -reset_password.text = Jei tai buvote jĹ«s, spustelÄ—kite toliau esanÄŤiÄ… nuorodÄ…, kad atkurtumÄ—te savo paskyrÄ… per %s: \ No newline at end of file +reset_password.text = Jei tai buvote jĹ«s, spustelÄ—kite toliau esanÄŤiÄ… nuorodÄ…, kad atkurtumÄ—te savo paskyrÄ… per %s: + +[heatmap] +contributions_one = ÄŻnašas +contributions_few = ÄŻnašai +less = MaĹľiau +more = Daugiau +number_of_contributions_in_the_last_12_months = %s ÄŻnašų per pastaruosius 12 mÄ—nesiĹł +contributions_zero = Ä®našų nÄ—ra +contributions_format = {contributions} {year} {month} {day} + +[aria] +navbar = Naršymo juosta +footer = PuslapinÄ— poraštÄ— +footer.software = Apie šiÄ… programinÄ™ ÄŻrangÄ… +footer.links = Nuorodos + +[editor] +buttons.quote.tooltip = Cituoti tekstÄ… +buttons.code.tooltip = PridÄ—ti kodÄ… +buttons.link.tooltip = PridÄ—ti nuorodÄ… +buttons.heading.tooltip = PridÄ—ti antraštÄ™ +buttons.bold.tooltip = PridÄ—ti pusjuodÄŻ tekstÄ… +buttons.italic.tooltip = PridÄ—ti kursyvinÄŻ tekstÄ… + +[error] +network_error = Tinklo klaida +server_internal = Vidinio serverio klaida + +[startpage] +app_desc = NesudÄ—tinga, savarankiškai teikiama „Git“ paslauga +install = Lengva ÄŻdiegti + +[install] +path = Kelias +err_admin_name_is_reserved = Administratoriaus naudotojo vardas netinkamas. Naudotojo vardas yra rezervuotas. +enable_update_checker = Ä®jungti naujinimĹł tikrintuvÄ… +env_config_keys = Aplinkos konfigĹ«racija +db_title = DuomenĹł bazÄ—s nustatymai +db_type = DuomenĹł bazÄ—s tipas +user = Naudotojo vardas +password = SlaptaĹľodis +db_name = DuomenĹł bazÄ—s pavadinimas +db_schema = Schema +ssl_mode = SSL +host = Pagrindinis komputeris +general_title = Bendrieji nustatymai +email_title = El. pašto nustatymai +federated_avatar_lookup.description = Ieškokite pseudoportretĹł naudojant „Libravatar“. +db_schema_helper = Palikite tuščiÄ…, jei tai numatytoji duomenĹł bazÄ— („public“). +err_empty_admin_password = Administratoriaus slaptaĹľodis negali bĹ«ti tuščias. +err_empty_admin_email = Administratoriaus el. paštas negali bĹ«ti tuščias. + +[explore] +go_to = Eiti ÄŻ +code = Kodas + +[auth] +remember_me = Prisiminti šį ÄŻrenginÄŻ +forgot_password_title = Pamirštas slaptaĹľodis +forgot_password = Pamiršote slaptaĹľodÄŻ? \ No newline at end of file diff --git a/options/locale/locale_nb_NO.ini b/options/locale/locale_nb_NO.ini index aae4ae788f..349d56ce13 100644 --- a/options/locale/locale_nb_NO.ini +++ b/options/locale/locale_nb_NO.ini @@ -23,4 +23,115 @@ language = SprĂĄk notifications = Varslinger create_new = Opprett… user_profile_and_more = Profil og innstillinger… -signed_in_as = Logget inn som \ No newline at end of file +signed_in_as = Logget inn som +confirm_delete_selected = Bekreft sletting av alle valgte elementer? +dashboard = Dashbord +download_logs = Last ned logger +copy_hash = Kopier hash +more_items = Flere elementer +passcode = Adgangskode +webauthn_insert_key = Skriv inn din sikkerhetsnøkkel +webauthn_use_twofa = Bruk tofaktorkode fra din mobil +organization = Organisasjon +mirror = Speil +new_mirror = Ny speiling +repository = Repositorium +new_project = Nytt prosjekt +new_project_column = Ny kolonne +webauthn_error = Klarte ikke lese sikkerhetsnøkkelen. +webauthn_unsupported_browser = Nettleseren din støtter ikke WebAuthn. +webauthn_error_unknown = En ukjent feil oppstod. Vennligst prøv igjen. +webauthn_error_insecure = WebAuhn støtter kun sikre forbindelser. For testing over HTTP kan du bruke verten "localhost" eller "127.0.0.1" +admin_panel = Nettsideadministrasjon +settings = Innstillinger +your_profile = Profil +your_starred = Stjernemerket +your_settings = Innstillinger +new_repo.title = Nytt repositorium +new_migrate.title = Ny migrasjon +new_org.title = Ny organisasjon +new_repo.link = Nytt repositorium +new_migrate.link = Ny migrasjon +new_org.link = Ny organisasjon +all = Alle +sources = Kilder +mirrors = Speilinger +activities = Aktiviteter +rss_feed = RSS feed +retry = Prøv igjen +rerun = Kjør pĂĄ nytt +rerun_all = Kjør alle jobber pĂĄ nytt +save = Lagre +cancel = Avbryt +forks = Forks +milestones = Milepæler +ok = OK +test = Test +loading = Laster inn… +error = Feil +go_back = GĂĄ tilbake +never = Aldri +invalid_data = Ugyldig data: %v +unknown = Ukjent +pin = Pin +artifacts = Artefakter +archived = Arkivert +concept_system_global = Global +add = Legg til +add_all = Legg til alle +remove = Fjern +remove_all = Fjern alle +remove_label_str = Fjern element "%s" +edit = Rediger +view = Vis +enabled = Aktivert +disabled = Deaktivert +locked = LĂĄst +copy = Kopier +copy_generic = Kopier til utklippstavlen +copy_url = Kopier URL +copy_content = Kopier innhold +copy_success = Kopiert! +copy_error = Kopiering mislyktes +copy_type_unsupported = Denne filtypen kan ikke kopieres +write = Skriv +preview = ForhĂĄndsvis +concept_user_individual = Individuell +concept_code_repository = Repositorium +concept_user_organization = Organisasjon +show_timestamps = Vis tidsstempler +show_log_seconds = Vis sekunder +show_full_screen = Vis fullskjerm +name = Navn +value = Verdi +filter = Filter +filter.clear = Tøm filtre +filter.is_archived = Arkivert +filter.not_archived = Ikke arkivert +filter.is_mirror = Speilinger +filter.not_mirror = Ikke speilinger +filter.is_template = Maler +filter.not_template = Ikke maler +filter.public = Offentlig +filter.private = Privat +explore = Utforsk +active_stopwatch = Aktiv tidsregistrering +home = Hjem +help = Hjelp +logo = Logo +sign_in = Logg inn +sign_in_with_provider = Logg inn med %s +sign_in_or = eller +sign_out = Logg ut +sign_up = Opprett konto +confirm_delete_artifact = Er du sikker pĂĄ at du vil slette artefakten "%s" ? + +[search] +search = Søk... +type_tooltip = Søketype +fuzzy = Fuzzy +union = Union + +[auth] +verify = Bekreft +sign_up_button = Opprett konto nĂĄ. \ No newline at end of file diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 0938d4099c..e13b1341c4 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1406,7 +1406,7 @@ commitstatus.failure=НеŃдача commitstatus.pending=Ожидание commitstatus.success=ĐŁŃпеŃно -ext_issues=ДоŃŃ‚ŃĐż ко внеŃним задачам +ext_issues=ВнеŃние задачи ext_issues.desc=СŃылка на внеŃнюю ŃиŃŃ‚ĐµĐĽŃ ĐľŃ‚Ńлеживания задач. projects=Проекты @@ -1587,9 +1587,9 @@ issues.no_content=ОпиŃание отŃŃŃ‚ŃтвŃет. issues.close=Закрыть Đ·Đ°Đ´Đ°Ń‡Ń 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.close_comment_issue=Закрыть комментарием issues.reopen_issue=Открыть Ńнова -issues.reopen_comment_issue=Прокомментировать и открыть Ńнова +issues.reopen_comment_issue=Открыть Ńнова комментарием issues.create_comment=Комментировать issues.closed_at=`задача была закрыта %[2]s` issues.reopened_at=`задача была открыта Ńнова %[2]s` @@ -1964,7 +1964,7 @@ signing.wont_sign.commitssigned=Слияние не бŃдет подпиŃан signing.wont_sign.approved=Слияние не бŃдет подпиŃано, так как Đ·Đ°ĐżŃ€ĐľŃ Đ˝Đ° Ńлияние не одобрен. signing.wont_sign.not_signed_in=Đ’Ń‹ не воŃли в ŃиŃтемŃ. -ext_wiki=ДоŃŃ‚ŃĐż ко внеŃней вики +ext_wiki=ВнеŃняя вики ext_wiki.desc=СŃылка на внеŃнюю вики. wiki=Вики @@ -3332,7 +3332,7 @@ config.allow_only_external_registration=РегиŃтрация только че config.enable_openid_signup=СаморегиŃтрация через OpenID config.enable_openid_signin=Вход через OpenID config.show_registration_button=Кнопка региŃтрации -config.require_sign_in_view=Для проŃмотра Ńодержимого необходима авторизация +config.require_sign_in_view=Требовать авторизацию для проŃмотра Ńодержимого config.mail_notify=Уведомления по ŃŤĐ». почте config.enable_captcha=CAPTCHA config.active_code_lives=Срок дейŃтвия кода активации Ńчётной запиŃи @@ -3963,4 +3963,8 @@ filepreview.lines = Строки Ń %[1]d по %[2]d в %[3]s filepreview.truncated = ПредпроŃмотр был обрезан [translation_meta] -test = хи-хи! \ No newline at end of file +test = хи-хи! + +[repo.permissions] +code.write = ЗапиŃŃŚ: отправка изменений в репозиторий, Ńоздание веток и тегов. +code.read = Чтение: Đ´ĐľŃŃ‚ŃĐż Đş иŃŃ…ĐľĐ´Đ˝ĐľĐĽŃ ĐşĐľĐ´Ń Ń€ĐµĐżĐľĐ·Đ¸Ń‚ĐľŃ€Đ¸ŃŹ и клонированию. \ No newline at end of file diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 1873b11478..7a1a77c3e5 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1427,7 +1427,7 @@ commitstatus.failure=失败 commitstatus.pending=待定 commitstatus.success=ć功 -ext_issues=访问外é¨ĺ·ĄĺŤ• +ext_issues=外é¨ĺ·ĄĺŤ• ext_issues.desc=链接ĺ°ĺ¤–é¨ĺ·ĄĺŤ•跟踪系统。 projects=项目 @@ -1608,9 +1608,9 @@ issues.no_content=没有ćŹäľ›čŻ´ćŽă€‚ issues.close=关闭工单 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.close_comment_issue=关闭评论 issues.reopen_issue=é‡Ťć–°ĺĽ€ĺŻ -issues.reopen_comment_issue=čŻ„č®şĺą¶é‡Ťć–°ĺĽ€ĺŻ +issues.reopen_comment_issue=重新打开评论 issues.create_comment=评论 issues.closed_at=`于 %[2]s 关闭此工单` issues.reopened_at=`é‡Ťć–°ć‰“ĺĽ€ć­¤é—®é˘ %[2]s` @@ -1999,7 +1999,7 @@ signing.wont_sign.commitssigned=ĺ并将不会被签ĺŤďĽŚĺ› ä¸şć‰€ćś‰ç›¸ĺ…łçš„ signing.wont_sign.approved=ĺ并将不会被签ĺŤďĽŚĺ› ä¸şĺ并请求未被批准。 signing.wont_sign.not_signed_in=您čżć˛ˇćś‰ç™»ĺ˝•。 -ext_wiki=访问外é¨ç™ľç§‘ +ext_wiki=外é¨ç™ľç§‘ ext_wiki.desc=链接ĺ°ĺ¤–é¨ wiki。 wiki=百科 @@ -2330,7 +2330,7 @@ settings.event_repository_desc=ĺ›ĺ»şć–ĺ é™¤ä»“ĺş“ settings.event_header_issue=工单事件 settings.event_issues=工单 settings.event_issues_desc=工单已打开ă€ĺ·˛ĺ…łé—­ă€ĺ·˛é‡Ťć–°ć‰“开ć–已编辑。 -settings.event_issue_assign=工单已ĺ†é…Ť +settings.event_issue_assign=工单已指派 settings.event_issue_assign_desc=工单已被指派ć–取ć¶ćŚ‡ć´ľă€‚ settings.event_issue_label=工单已ĺ†ç±» settings.event_issue_label_desc=工单标签被更新ć–清除。 @@ -3818,7 +3818,7 @@ management=ĺŻ†é’Ąç®ˇç† [actions] actions=Actions -unit.desc=使用 Forgejo Actions 管ç†é›†ćçš„ CI/CD ç®ˇé“ +unit.desc=使用 Forgejo Actions 管ç†é›†ćçš„ CI/CD 管é“。 status.unknown=未知 status.waiting=等待中 @@ -3980,4 +3980,24 @@ filepreview.lines = %[3]s 中的第 %[1]d ĺ° %[2]d 行 filepreview.truncated = 预č§ĺ·˛č˘«ćŞć–­ [translation_meta] -test = 好的 \ No newline at end of file +test = 好的 + +[repo.permissions] +code.write = 写入:推é€ĺ°ä»“库,ĺ›ĺ»şĺ†ć”Żĺ’Ść ‡ç­ľă€‚ +code.read = 读取:访问并克隆仓库的代ç ă€‚ +actions.read = 读取:查看集ćçš„ CI/CD 管é“及其日志。 +issues.write = 写入:关闭工单并管ç†ĺ…数据,如标签ă€é‡Śç¨‹ç˘‘ă€ćŚ‡ć´ľćĺ‘ă€ćŞć­˘ć—Ąćśźĺ’Śäľťčµ–。 +releases.write = 写入:发ĺ¸ă€çĽ–辑和ĺ é™¤ç‰ćś¬ĺŹ‘ĺ¸ĺŹŠĺ…¶čµ„äş§ă€‚ +issues.read = 读取:é…读并ĺ›ĺ»şĺ·ĄĺŤ•和评论。 +pulls.read = 读取:é…读并ĺ›ĺ»şĺ并请求。 +releases.read = 读取:查看并下载ç‰ćś¬ĺŹ‘ĺ¸ă€‚ +wiki.read = 读取:é…读集ć的百科及其历史。 +wiki.write = 写入:在集ć的百科中ĺ›ĺ»şă€ć›´ć–°ĺ’Śĺ é™¤éˇµéť˘ă€‚ +projects.read = 读取:访问仓库项目看板。 +packages.read = 读取:查看并下载指派给仓库的软件包。 +packages.write = 写入:发ĺ¸ĺą¶ĺ é™¤ćŚ‡ć´ľç»™ä»“ĺş“çš„č˝Żä»¶ĺŚ…ă€‚ +actions.write = 写入:手动触发ă€é‡ŤĺŻă€ĺŹ–ć¶ć–批准待处ç†çš„ CI/CD 管é“。 +ext_issues = 访问外é¨ĺ·ĄĺŤ•系统的链接。ćťé™ç”±ĺ¤–é¨ç®ˇç†ă€‚ +ext_wiki = 访问外é¨ç™ľç§‘的链接。ćťé™ç”±ĺ¤–é¨ç®ˇç†ă€‚ +projects.write = 写入:ĺ›ĺ»şéˇąç›®ĺ’Śĺ—并进行编辑。 +pulls.write = 写入:关闭ĺ并请求并管ç†ĺ…数据,如标签ă€é‡Śç¨‹ç˘‘ă€ćŚ‡ć´ľćĺ‘ă€ćŞć­˘ć—Ąćśźĺ’Śäľťčµ–。 \ No newline at end of file From a75862bd7d76654a4706769a01f693b425f34b05 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 29 Sep 2024 08:03:17 +0000 Subject: [PATCH 016/166] Update dependency webpack to v5.95.0 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7dd1cba03a..7da312bb80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "vue-chartjs": "5.3.1", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.94.0", + "webpack": "5.95.0", "webpack-cli": "5.1.4", "wrap-ansi": "9.0.0" }, @@ -16379,9 +16379,9 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.5", @@ -16494,9 +16494,9 @@ } }, "node_modules/webpack/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "license": "MIT" }, "node_modules/webpack/node_modules/ajv": { diff --git a/package.json b/package.json index 12c6a05c12..a9cf80684b 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "vue-chartjs": "5.3.1", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.94.0", + "webpack": "5.95.0", "webpack-cli": "5.1.4", "wrap-ansi": "9.0.0" }, From 14c7055494b995476d9d2ec1948784bf36dd9e4d Mon Sep 17 00:00:00 2001 From: ChristopherHX Date: Sun, 22 Sep 2024 13:01:09 +0200 Subject: [PATCH 017/166] Fix artifact v4 upload above 8MB (#31664) Multiple chunks are uploaded with type "block" without using "appendBlock" and eventually out of order for bigger uploads. 8MB seems to be the chunk size This change parses the blockList uploaded after all blocks to get the final artifact size and order them correctly before calculating the sha256 checksum over all blocks Fixes #31354 (cherry picked from commit b594cec2bda6f861effedb2e8e0a7ebba191c0e9) Conflicts: routers/api/actions/artifactsv4.go conflict because of Refactor AppURL usage (#30885) 67c1a07285008cc00036a87cef966c3bd519a50c that was not cherry-picked in Forgejo the resolution consist of removing the extra ctx argument --- routers/api/actions/artifacts_chunks.go | 50 +++++- routers/api/actions/artifactsv4.go | 144 +++++++++++++----- .../api_actions_artifact_v4_test.go | 130 ++++++++++++++++ 3 files changed, 285 insertions(+), 39 deletions(-) diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go index b0c96585cb..cdb56584b8 100644 --- a/routers/api/actions/artifacts_chunks.go +++ b/routers/api/actions/artifacts_chunks.go @@ -123,6 +123,54 @@ func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chun return chunksMap, nil } +func listChunksByRunIDV4(st storage.ObjectStorage, runID, artifactID int64, blist *BlockList) ([]*chunkFileItem, error) { + storageDir := fmt.Sprintf("tmpv4%d", runID) + var chunks []*chunkFileItem + chunkMap := map[string]*chunkFileItem{} + dummy := &chunkFileItem{} + for _, name := range blist.Latest { + chunkMap[name] = dummy + } + if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error { + baseName := filepath.Base(fpath) + if !strings.HasPrefix(baseName, "block-") { + return nil + } + // when read chunks from storage, it only contains storage dir and basename, + // no matter the subdirectory setting in storage config + item := chunkFileItem{Path: storageDir + "/" + baseName, ArtifactID: artifactID} + var size int64 + var b64chunkName string + if _, err := fmt.Sscanf(baseName, "block-%d-%d-%s", &item.RunID, &size, &b64chunkName); err != nil { + return fmt.Errorf("parse content range error: %v", err) + } + rchunkName, err := base64.URLEncoding.DecodeString(b64chunkName) + if err != nil { + return fmt.Errorf("failed to parse chunkName: %v", err) + } + chunkName := string(rchunkName) + item.End = item.Start + size - 1 + if _, ok := chunkMap[chunkName]; ok { + chunkMap[chunkName] = &item + } + return nil + }); err != nil { + return nil, err + } + for i, name := range blist.Latest { + chunk, ok := chunkMap[name] + if !ok || chunk.Path == "" { + return nil, fmt.Errorf("missing Chunk (%d/%d): %s", i, len(blist.Latest), name) + } + chunks = append(chunks, chunk) + if i > 0 { + chunk.Start = chunkMap[blist.Latest[i-1]].End + 1 + chunk.End += chunk.Start + } + } + return chunks, nil +} + func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int64, artifactName string) error { // read all db artifacts by name artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{ @@ -230,7 +278,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st rawChecksum := hash.Sum(nil) actualChecksum := hex.EncodeToString(rawChecksum) if !strings.HasSuffix(checksum, actualChecksum) { - return fmt.Errorf("update artifact error checksum is invalid") + return fmt.Errorf("update artifact error checksum is invalid %v vs %v", checksum, actualChecksum) } } diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go index 7b2f9c4360..677e89da2f 100644 --- a/routers/api/actions/artifactsv4.go +++ b/routers/api/actions/artifactsv4.go @@ -24,8 +24,15 @@ package actions // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=block // 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock -// 1.4. Unknown xml payload to Blobstorage (unauthenticated request), ignored for now +// 1.4. BlockList xml payload to Blobstorage (unauthenticated request) +// Files of about 800MB are parallel in parallel and / or out of order, this file is needed to enshure the correct order // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList +// Request +// +// +// blockId1 +// blockId2 +// // 1.5. FinalizeArtifact // Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact // Request @@ -82,6 +89,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" + "encoding/xml" "fmt" "io" "net/http" @@ -153,31 +161,34 @@ func ArtifactsV4Routes(prefix string) *web.Route { return m } -func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID int64) []byte { +func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID, artifactID int64) []byte { mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret()) mac.Write([]byte(endp)) mac.Write([]byte(expires)) mac.Write([]byte(artifactName)) mac.Write([]byte(fmt.Sprint(taskID))) + mac.Write([]byte(fmt.Sprint(artifactID))) return mac.Sum(nil) } -func (r artifactV4Routes) buildArtifactURL(endp, artifactName string, taskID int64) string { +func (r artifactV4Routes) buildArtifactURL(endp, artifactName string, taskID, artifactID int64) string { expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST") uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(r.prefix, "/") + - "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) + "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) + "&artifactID=" + fmt.Sprint(artifactID) return uploadURL } func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) { rawTaskID := ctx.Req.URL.Query().Get("taskID") + rawArtifactID := ctx.Req.URL.Query().Get("artifactID") sig := ctx.Req.URL.Query().Get("sig") expires := ctx.Req.URL.Query().Get("expires") artifactName := ctx.Req.URL.Query().Get("artifactName") dsig, _ := base64.URLEncoding.DecodeString(sig) taskID, _ := strconv.ParseInt(rawTaskID, 10, 64) + artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) - expecedsig := r.buildSignature(endp, expires, artifactName, taskID) + expecedsig := r.buildSignature(endp, expires, artifactName, taskID, artifactID) if !hmac.Equal(dsig, expecedsig) { log.Error("Error unauthorized") ctx.Error(http.StatusUnauthorized, "Error unauthorized") @@ -272,6 +283,8 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) { return } artifact.ContentEncoding = ArtifactV4ContentEncoding + artifact.FileSize = 0 + artifact.FileCompressedSize = 0 if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { log.Error("Error UpdateArtifactByID: %v", err) ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") @@ -280,7 +293,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) { respData := CreateArtifactResponse{ Ok: true, - SignedUploadUrl: r.buildArtifactURL("UploadArtifact", artifactName, ctx.ActionTask.ID), + SignedUploadUrl: r.buildArtifactURL("UploadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID), } r.sendProtbufBody(ctx, &respData) } @@ -306,38 +319,77 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) { comp := ctx.Req.URL.Query().Get("comp") switch comp { case "block", "appendBlock": - // get artifact by name - artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) - if err != nil { - log.Error("Error artifact not found: %v", err) - ctx.Error(http.StatusNotFound, "Error artifact not found") - return - } + blockid := ctx.Req.URL.Query().Get("blockid") + if blockid == "" { + // get artifact by name + artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) + if err != nil { + log.Error("Error artifact not found: %v", err) + ctx.Error(http.StatusNotFound, "Error artifact not found") + return + } - if comp == "block" { - artifact.FileSize = 0 - artifact.FileCompressedSize = 0 + _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) + if err != nil { + log.Error("Error runner api getting task: task is not running") + ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") + return + } + artifact.FileCompressedSize += ctx.Req.ContentLength + artifact.FileSize += ctx.Req.ContentLength + if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { + log.Error("Error UpdateArtifactByID: %v", err) + ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") + return + } + } else { + _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/block-%d-%d-%s", task.Job.RunID, task.Job.RunID, ctx.Req.ContentLength, base64.URLEncoding.EncodeToString([]byte(blockid))), ctx.Req.Body, -1) + if err != nil { + log.Error("Error runner api getting task: task is not running") + ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") + return + } } - - _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) + ctx.JSON(http.StatusCreated, "appended") + case "blocklist": + rawArtifactID := ctx.Req.URL.Query().Get("artifactID") + artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) + _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/%d-%d-blocklist", task.Job.RunID, task.Job.RunID, artifactID), ctx.Req.Body, -1) if err != nil { log.Error("Error runner api getting task: task is not running") ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") return } - artifact.FileCompressedSize += ctx.Req.ContentLength - artifact.FileSize += ctx.Req.ContentLength - if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { - log.Error("Error UpdateArtifactByID: %v", err) - ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") - return - } - ctx.JSON(http.StatusCreated, "appended") - case "blocklist": ctx.JSON(http.StatusCreated, "created") } } +type BlockList struct { + Latest []string `xml:"Latest"` +} + +type Latest struct { + Value string `xml:",chardata"` +} + +func (r *artifactV4Routes) readBlockList(runID, artifactID int64) (*BlockList, error) { + blockListName := fmt.Sprintf("tmpv4%d/%d-%d-blocklist", runID, runID, artifactID) + s, err := r.fs.Open(blockListName) + if err != nil { + return nil, err + } + + xdec := xml.NewDecoder(s) + blockList := &BlockList{} + err = xdec.Decode(blockList) + + delerr := r.fs.Delete(blockListName) + if delerr != nil { + log.Warn("Failed to delete blockList %s: %v", blockListName, delerr) + } + return blockList, err +} + func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { var req FinalizeArtifactRequest @@ -356,18 +408,34 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { ctx.Error(http.StatusNotFound, "Error artifact not found") return } - chunkMap, err := listChunksByRunID(r.fs, runID) + + var chunks []*chunkFileItem + blockList, err := r.readBlockList(runID, artifact.ID) if err != nil { - log.Error("Error merge chunks: %v", err) - ctx.Error(http.StatusInternalServerError, "Error merge chunks") - return - } - chunks, ok := chunkMap[artifact.ID] - if !ok { - log.Error("Error merge chunks") - ctx.Error(http.StatusInternalServerError, "Error merge chunks") - return + log.Warn("Failed to read BlockList, fallback to old behavior: %v", err) + chunkMap, err := listChunksByRunID(r.fs, runID) + if err != nil { + log.Error("Error merge chunks: %v", err) + ctx.Error(http.StatusInternalServerError, "Error merge chunks") + return + } + chunks, ok = chunkMap[artifact.ID] + if !ok { + log.Error("Error merge chunks") + ctx.Error(http.StatusInternalServerError, "Error merge chunks") + return + } + } else { + chunks, err = listChunksByRunIDV4(r.fs, runID, artifact.ID, blockList) + if err != nil { + log.Error("Error merge chunks: %v", err) + ctx.Error(http.StatusInternalServerError, "Error merge chunks") + return + } + artifact.FileSize = chunks[len(chunks)-1].End + 1 + artifact.FileCompressedSize = chunks[len(chunks)-1].End + 1 } + checksum := "" if req.Hash != nil { checksum = req.Hash.Value @@ -468,7 +536,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) { } } if respData.SignedUrl == "" { - respData.SignedUrl = r.buildArtifactURL("DownloadArtifact", artifactName, ctx.ActionTask.ID) + respData.SignedUrl = r.buildArtifactURL("DownloadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID) } r.sendProtbufBody(ctx, &respData) } diff --git a/tests/integration/api_actions_artifact_v4_test.go b/tests/integration/api_actions_artifact_v4_test.go index b2c25a2e70..96668b1ddf 100644 --- a/tests/integration/api_actions_artifact_v4_test.go +++ b/tests/integration/api_actions_artifact_v4_test.go @@ -7,12 +7,14 @@ import ( "bytes" "crypto/sha256" "encoding/hex" + "encoding/xml" "io" "net/http" "strings" "testing" "time" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/routers/api/actions" actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/tests" @@ -175,6 +177,134 @@ func TestActionsArtifactV4UploadSingleFileWithRetentionDays(t *testing.T) { assert.True(t, finalizeResp.Ok) } +func TestActionsArtifactV4UploadSingleFileWithPotentialHarmfulBlockID(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + token, err := actions_service.CreateAuthorizationToken(48, 792, 193) + assert.NoError(t, err) + + // acquire artifact upload url + req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{ + Version: 4, + Name: "artifactWithPotentialHarmfulBlockID", + WorkflowRunBackendId: "792", + WorkflowJobRunBackendId: "193", + })).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var uploadResp actions.CreateArtifactResponse + protojson.Unmarshal(resp.Body.Bytes(), &uploadResp) + assert.True(t, uploadResp.Ok) + assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact") + + // get upload urls + idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/") + url := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=%2f..%2fmyfile" + blockListURL := uploadResp.SignedUploadUrl[idx:] + "&comp=blocklist" + + // upload artifact chunk + body := strings.Repeat("A", 1024) + req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)) + MakeRequest(t, req, http.StatusCreated) + + // verify that the exploit didn't work + _, err = storage.Actions.Stat("myfile") + assert.Error(t, err) + + // upload artifact blockList + blockList := &actions.BlockList{ + Latest: []string{ + "/../myfile", + }, + } + rawBlockList, err := xml.Marshal(blockList) + assert.NoError(t, err) + req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList)) + MakeRequest(t, req, http.StatusCreated) + + t.Logf("Create artifact confirm") + + sha := sha256.Sum256([]byte(body)) + + // confirm artifact upload + req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{ + Name: "artifactWithPotentialHarmfulBlockID", + Size: 1024, + Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha[:])), + WorkflowRunBackendId: "792", + WorkflowJobRunBackendId: "193", + })). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + var finalizeResp actions.FinalizeArtifactResponse + protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp) + assert.True(t, finalizeResp.Ok) +} + +func TestActionsArtifactV4UploadSingleFileWithChunksOutOfOrder(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + token, err := actions_service.CreateAuthorizationToken(48, 792, 193) + assert.NoError(t, err) + + // acquire artifact upload url + req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{ + Version: 4, + Name: "artifactWithChunksOutOfOrder", + WorkflowRunBackendId: "792", + WorkflowJobRunBackendId: "193", + })).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var uploadResp actions.CreateArtifactResponse + protojson.Unmarshal(resp.Body.Bytes(), &uploadResp) + assert.True(t, uploadResp.Ok) + assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact") + + // get upload urls + idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/") + block1URL := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=block1" + block2URL := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=block2" + blockListURL := uploadResp.SignedUploadUrl[idx:] + "&comp=blocklist" + + // upload artifact chunks + bodyb := strings.Repeat("B", 1024) + req = NewRequestWithBody(t, "PUT", block2URL, strings.NewReader(bodyb)) + MakeRequest(t, req, http.StatusCreated) + + bodya := strings.Repeat("A", 1024) + req = NewRequestWithBody(t, "PUT", block1URL, strings.NewReader(bodya)) + MakeRequest(t, req, http.StatusCreated) + + // upload artifact blockList + blockList := &actions.BlockList{ + Latest: []string{ + "block1", + "block2", + }, + } + rawBlockList, err := xml.Marshal(blockList) + assert.NoError(t, err) + req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList)) + MakeRequest(t, req, http.StatusCreated) + + t.Logf("Create artifact confirm") + + sha := sha256.Sum256([]byte(bodya + bodyb)) + + // confirm artifact upload + req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{ + Name: "artifactWithChunksOutOfOrder", + Size: 2048, + Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha[:])), + WorkflowRunBackendId: "792", + WorkflowJobRunBackendId: "193", + })). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + var finalizeResp actions.FinalizeArtifactResponse + protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp) + assert.True(t, finalizeResp.Ok) +} + func TestActionsArtifactV4DownloadSingle(t *testing.T) { defer tests.PrepareTestEnv(t)() From b2483b2ae031bbfd8829595fbff19cd323d80e7f Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sun, 29 Sep 2024 09:58:47 +0200 Subject: [PATCH 018/166] Fix artifact v4 upload above 8MB (#31664) (fix lint errors) --- tests/integration/api_actions_artifact_v4_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/integration/api_actions_artifact_v4_test.go b/tests/integration/api_actions_artifact_v4_test.go index 96668b1ddf..f55250f6c1 100644 --- a/tests/integration/api_actions_artifact_v4_test.go +++ b/tests/integration/api_actions_artifact_v4_test.go @@ -181,7 +181,7 @@ func TestActionsArtifactV4UploadSingleFileWithPotentialHarmfulBlockID(t *testing defer tests.PrepareTestEnv(t)() token, err := actions_service.CreateAuthorizationToken(48, 792, 193) - assert.NoError(t, err) + require.NoError(t, err) // acquire artifact upload url req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{ @@ -208,7 +208,7 @@ func TestActionsArtifactV4UploadSingleFileWithPotentialHarmfulBlockID(t *testing // verify that the exploit didn't work _, err = storage.Actions.Stat("myfile") - assert.Error(t, err) + require.Error(t, err) // upload artifact blockList blockList := &actions.BlockList{ @@ -217,7 +217,7 @@ func TestActionsArtifactV4UploadSingleFileWithPotentialHarmfulBlockID(t *testing }, } rawBlockList, err := xml.Marshal(blockList) - assert.NoError(t, err) + require.NoError(t, err) req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList)) MakeRequest(t, req, http.StatusCreated) @@ -244,7 +244,7 @@ func TestActionsArtifactV4UploadSingleFileWithChunksOutOfOrder(t *testing.T) { defer tests.PrepareTestEnv(t)() token, err := actions_service.CreateAuthorizationToken(48, 792, 193) - assert.NoError(t, err) + require.NoError(t, err) // acquire artifact upload url req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{ @@ -282,7 +282,7 @@ func TestActionsArtifactV4UploadSingleFileWithChunksOutOfOrder(t *testing.T) { }, } rawBlockList, err := xml.Marshal(blockList) - assert.NoError(t, err) + require.NoError(t, err) req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList)) MakeRequest(t, req, http.StatusCreated) From b28a070a528b2df5472018d296e4e9d9e128b3bc Mon Sep 17 00:00:00 2001 From: cloudchamb3r Date: Tue, 24 Sep 2024 02:09:57 +0900 Subject: [PATCH 019/166] Fix Bug in Issue/pulls list (#32081) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #32080 ## After ### for opened issues Screenshot 2024-09-19 at 6 29 31 PM ### for closed issues Screenshot 2024-09-19 at 6 29 37 PM ### for all issues Screenshot 2024-09-20 at 12 07 12 PM (cherry picked from commit e1f0598c8f5af5ac95f5e13b74fbab99506762db) --- routers/web/repo/issue.go | 1 + templates/repo/issue/filter_actions.tmpl | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 01fd1e2725..5d13ccc97c 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -476,6 +476,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["PosterID"] = posterID ctx.Data["IsFuzzy"] = isFuzzy ctx.Data["Keyword"] = keyword + ctx.Data["IsShowClosed"] = isShowClosed switch { case isShowClosed.Value(): ctx.Data["State"] = "closed" diff --git a/templates/repo/issue/filter_actions.tmpl b/templates/repo/issue/filter_actions.tmpl index a341448bcc..58b1ef8ecd 100644 --- a/templates/repo/issue/filter_actions.tmpl +++ b/templates/repo/issue/filter_actions.tmpl @@ -1,9 +1,9 @@