From 0ff2c11432f7e1bfee197b067cfabbf8c2e8a5a7 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 10 Feb 2025 04:39:54 +0800 Subject: [PATCH 01/79] Remove "class-name" from svg icon (#33540) Only use "class" attribute (cherry picked from commit 06088ec672a053b11a20ca6dd21588380d67f396) --- web_src/js/components/ActionRunStatus.vue | 12 ++++++------ web_src/js/components/DashboardRepoList.vue | 18 +++++++++--------- .../js/components/RepoBranchTagSelector.vue | 6 +++--- web_src/js/svg.js | 11 +---------- web_src/js/svg.test.js | 3 +-- 5 files changed, 20 insertions(+), 30 deletions(-) diff --git a/web_src/js/components/ActionRunStatus.vue b/web_src/js/components/ActionRunStatus.vue index eb7f780fbe..db380f0038 100644 --- a/web_src/js/components/ActionRunStatus.vue +++ b/web_src/js/components/ActionRunStatus.vue @@ -29,12 +29,12 @@ export default { diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 0d8396b6f3..35066d737e 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -359,7 +359,7 @@ export default sfc; // activate the IDE's Vue plugin otherwise if the "input" handles click event for intermediate status, it breaks the internal state--> @@ -368,7 +368,7 @@ export default sfc; // activate the IDE's Vue plugin
@@ -405,7 +405,7 @@ export default sfc; // activate the IDE's Vue plugin @@ -424,7 +424,7 @@ export default sfc; // activate the IDE's Vue plugin class="item navigation tw-py-1" :class="{'disabled': page === 1}" @click="changePage(1)" :title="textFirstPage" > - + - + - + @@ -454,7 +454,7 @@ export default sfc; // activate the IDE's Vue plugin diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue index bfba2037cc..660551b8c7 100644 --- a/web_src/js/components/RepoBranchTagSelector.vue +++ b/web_src/js/components/RepoBranchTagSelector.vue @@ -256,7 +256,7 @@ export default sfc; // activate IDE's Vue plugin {{ refNameText }} - + From 6fb069cc50d940e09f7c7235736acf1b7bf72aea Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Tue, 25 Feb 2025 18:44:41 +0000 Subject: [PATCH 09/79] fix(repo): return code 400 instead of 500 for invalid archive type (#7029) The error handled might be non-nil only when format is bad, which is 400 (Bad Request) Fixes #7026 Signed-off-by: Dmitrii Sharshakov Co-authored-by: 0ko <0ko@noreply.codeberg.org> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7029 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Dmitrii Sharshakov Co-committed-by: Dmitrii Sharshakov --- routers/web/repo/repo.go | 4 ++-- tests/integration/repo_archive_test.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 1c4fb39546..2e8ca61bf5 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -474,7 +474,7 @@ func Download(ctx *context.Context) { uri := ctx.Params("*") ext, tp, err := archiver_service.ParseFileName(uri) if err != nil { - ctx.ServerError("ParseFileName", err) + ctx.NotFound("ParseFileName", err) return } aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp) @@ -554,7 +554,7 @@ func InitiateDownload(ctx *context.Context) { uri := ctx.Params("*") ext, tp, err := archiver_service.ParseFileName(uri) if err != nil { - ctx.ServerError("ParseFileName", err) + ctx.NotFound("ParseFileName", err) return } aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp) diff --git a/tests/integration/repo_archive_test.go b/tests/integration/repo_archive_test.go index a5c076952c..aea500b9b1 100644 --- a/tests/integration/repo_archive_test.go +++ b/tests/integration/repo_archive_test.go @@ -39,6 +39,10 @@ func TestRepoDownloadArchive(t *testing.T) { require.NoError(t, err) assert.Empty(t, resp.Header().Get("Content-Encoding")) assert.Len(t, bs, 320) + + // Verify that unrecognized archive type returns 404 + req = NewRequest(t, "GET", "/user2/repo1/archive/master.invalid") + MakeRequest(t, req, http.StatusNotFound) } func TestRepoDownloadArchiveSubdir(t *testing.T) { From 3372db660c6c8223deba865e31412203dec081de Mon Sep 17 00:00:00 2001 From: Otto Richter Date: Tue, 25 Feb 2025 20:37:28 +0000 Subject: [PATCH 10/79] enh: admin user view (#7062) - directly click email address - show prohibited login state ## Testing - visit admin panel - visit a user - click on "view details" - check that email address is clickable and opens local email editor - check that "suspended account" status is visible ![](/attachments/251789ec-0cf1-41f9-bffe-48693917c467) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7062 Reviewed-by: Gusted Co-authored-by: Otto Richter Co-committed-by: Otto Richter --- templates/admin/user/view_details.tmpl | 8 ++++++++ templates/admin/user/view_emails.tmpl | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/templates/admin/user/view_details.tmpl b/templates/admin/user/view_details.tmpl index be2f32b5ec..c394b4bd3d 100644 --- a/templates/admin/user/view_details.tmpl +++ b/templates/admin/user/view_details.tmpl @@ -26,6 +26,14 @@ {{svg "octicon-x"}} {{end}} +
+ {{ctx.Locale.Tr "admin.users.prohibit_login"}}: + {{if .User.ProhibitLogin}} + {{svg "octicon-check"}} + {{else}} + {{svg "octicon-x"}} + {{end}} +
{{ctx.Locale.Tr "admin.users.restricted"}}: {{if .User.IsRestricted}} diff --git a/templates/admin/user/view_emails.tmpl b/templates/admin/user/view_emails.tmpl index 22ce305a88..7e77206f1c 100644 --- a/templates/admin/user/view_emails.tmpl +++ b/templates/admin/user/view_emails.tmpl @@ -3,7 +3,7 @@
- {{.Email}} + {{.Email}} {{if .IsPrimary}}
{{ctx.Locale.Tr "settings.primary"}}
{{end}} From 6dad457552d259ab39b7e172c21865398163d84a Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Tue, 25 Feb 2025 20:40:16 +0000 Subject: [PATCH 11/79] Improve "URL" handling in markdown editor (#7006) The button to insert an URL now opens a dialog prompting for the two components, the URL and the description. Any existing text selection is taken into account to pre-fill the description field. Closes: #6731 ![image](/attachments/ca1f7767-5fd6-4c38-8c7a-83d893523de2) Co-authored-by: Otto Richter Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7006 Reviewed-by: Otto Co-authored-by: Lucas Schwiderski Co-committed-by: Lucas Schwiderski --- options/locale/locale_en-US.ini | 5 ++ templates/shared/combomarkdowneditor.tmpl | 28 +++++++++- tests/e2e/markdown-editor.test.e2e.ts | 28 ++++++++++ .../js/features/comp/ComboMarkdownEditor.js | 54 +++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index bd7b91cd5a..29c696f29f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -232,6 +232,11 @@ table_modal.placeholder.content = Content table_modal.label.rows = Rows table_modal.label.columns = Columns +link_modal.header = Add a link +link_modal.url = Url +link_modal.description = Description +link_modal.paste_reminder = Hint: With a URL in your clipboard, you can paste directly into the editor to create a link. + [filter] string.asc = A - Z string.desc = Z - A diff --git a/templates/shared/combomarkdowneditor.tmpl b/templates/shared/combomarkdowneditor.tmpl index 0620cca65f..1ea22660f7 100644 --- a/templates/shared/combomarkdowneditor.tmpl +++ b/templates/shared/combomarkdowneditor.tmpl @@ -29,7 +29,7 @@ Template Attributes:
{{svg "octicon-quote"}} {{svg "octicon-code"}} - {{svg "octicon-link"}} +
{{svg "octicon-list-unordered"}} @@ -88,4 +88,30 @@ Template Attributes:
+ +
diff --git a/tests/e2e/markdown-editor.test.e2e.ts b/tests/e2e/markdown-editor.test.e2e.ts index 35e9de2ea6..39ced25bc3 100644 --- a/tests/e2e/markdown-editor.test.e2e.ts +++ b/tests/e2e/markdown-editor.test.e2e.ts @@ -5,6 +5,7 @@ // @watch end import {expect} from '@playwright/test'; +import {accessibilityCheck} from './shared/accessibility.ts'; import {save_visual, test} from './utils_e2e.ts'; test.use({user: 'user2'}); @@ -224,6 +225,33 @@ test('markdown insert table', async ({page}) => { await save_visual(page); }); +test('markdown insert link', async ({page}) => { + const response = await page.goto('/user2/repo1/issues/new'); + expect(response?.status()).toBe(200); + + const newLinkButton = page.locator('button[data-md-action="new-link"]'); + await newLinkButton.click(); + + const newLinkModal = page.locator('div[data-markdown-link-modal-id="0"]'); + await expect(newLinkModal).toBeVisible(); + await accessibilityCheck({page}, ['[data-modal-name="new-markdown-link"]'], [], []); + await save_visual(page); + + const url = 'https://example.com'; + const description = 'Where does this lead?'; + + await newLinkModal.locator('input[name="link-url"]').fill(url); + await newLinkModal.locator('input[name="link-description"]').fill(description); + + await newLinkModal.locator('button[data-selector-name="ok-button"]').click(); + + await expect(newLinkModal).toBeHidden(); + + const textarea = page.locator('textarea[name=content]'); + await expect(textarea).toHaveValue(`[${description}](${url})`); + await save_visual(page); +}); + test('text expander has higher prio then prefix continuation', async ({page}) => { const response = await page.goto('/user2/repo1/issues/new'); expect(response?.status()).toBe(200); diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 89a252f6f3..efd068f354 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -49,6 +49,7 @@ class ComboMarkdownEditor { this.setupDropzone(); this.setupTextarea(); this.setupTableInserter(); + this.setupLinkInserter(); await this.switchToUserPreference(); @@ -93,6 +94,7 @@ class ComboMarkdownEditor { this.indentSelection(true); }); this.textareaMarkdownToolbar.querySelector('button[data-md-action="new-table"]')?.setAttribute('data-modal', `div[data-markdown-table-modal-id="${elementIdCounter}"]`); + this.textareaMarkdownToolbar.querySelector('button[data-md-action="new-link"]')?.setAttribute('data-modal', `div[data-markdown-link-modal-id="${elementIdCounter}"]`); this.textarea.addEventListener('keydown', (e) => { if (e.shiftKey) { @@ -228,6 +230,58 @@ class ComboMarkdownEditor { button.addEventListener('click', this.addNewTable); } + addNewLink(event) { + const elementId = event.target.getAttribute('data-element-id'); + const newLinkModal = document.querySelector(`div[data-markdown-link-modal-id="${elementId}"]`); + const form = newLinkModal.querySelector('div[data-selector-name="form"]'); + + // Validate input fields + for (const currentInput of form.querySelectorAll('input')) { + if (!currentInput.checkValidity()) { + currentInput.reportValidity(); + return; + } + } + + const url = form.querySelector('input[name="link-url"]').value; + const description = form.querySelector('input[name="link-description"]').value; + + const code = `[${description}](${url})`; + + replaceTextareaSelection(document.getElementById(`_combo_markdown_editor_${elementId}`), code); + + // Close the modal then clear its fields in case the user wants to add another one. + newLinkModal.querySelector('button[data-selector-name="cancel-button"]').click(); + form.querySelector('input[name="link-url"]').value = ''; + form.querySelector('input[name="link-description"]').value = ''; + } + + setupLinkInserter() { + const newLinkModal = this.container.querySelector('div[data-modal-name="new-markdown-link"]'); + newLinkModal.setAttribute('data-markdown-link-modal-id', elementIdCounter); + const textarea = document.getElementById(`_combo_markdown_editor_${elementIdCounter}`); + + $(newLinkModal).modal({ + // Pre-fill the description field from the selection to create behavior similar + // to pasting an URL over selected text. + onShow: () => { + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + + if (start !== end) { + const selection = textarea.value.slice(start ?? undefined, end ?? undefined); + newLinkModal.querySelector('input[name="link-description"]').value = selection; + } else { + newLinkModal.querySelector('input[name="link-description"]').value = ''; + } + }, + }); + + const button = newLinkModal.querySelector('button[data-selector-name="ok-button"]'); + button.setAttribute('data-element-id', elementIdCounter); + button.addEventListener('click', this.addNewLink); + } + prepareEasyMDEToolbarActions() { this.easyMDEToolbarDefault = [ 'bold', 'italic', 'strikethrough', '|', 'heading-1', 'heading-2', 'heading-3', From e09ea821bd3dee3694d10ab305b8fbe7b3f9bd90 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 16 Feb 2025 19:18:00 -0800 Subject: [PATCH 12/79] Add API to support link package to repository and unlink it (#33481) Fix #21062 --------- Co-authored-by: Zettat123 (cherry picked from commit 5df9fd3e9c6ae7f848da65dbe9b9d321f29c003a) --- models/packages/package.go | 5 + routers/api/v1/api.go | 18 ++-- routers/api/v1/packages/package.go | 122 +++++++++++++++++++++++++ services/packages/package_update.go | 78 ++++++++++++++++ templates/swagger/v1_json.tmpl | 87 ++++++++++++++++++ tests/integration/api_packages_test.go | 38 ++++++-- 6 files changed, 334 insertions(+), 14 deletions(-) create mode 100644 services/packages/package_update.go diff --git a/models/packages/package.go b/models/packages/package.go index fd408f8bef..874c7c2847 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -242,6 +242,11 @@ func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error { return err } +func UnlinkRepository(ctx context.Context, packageID int64) error { + _, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: 0}) + return err +} + // UnlinkRepositoryFromAllPackages unlinks every package from the repository func UnlinkRepositoryFromAllPackages(ctx context.Context, repoID int64) error { _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Cols("repo_id").Update(&Package{}) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 684686b1e2..3791a6fbba 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1485,13 +1485,19 @@ func Routes() *web.Route { // NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs m.Group("/packages/{username}", func() { - m.Group("/{type}/{name}/{version}", func() { - m.Get("", reqToken(), packages.GetPackage) - m.Delete("", reqToken(), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage) - m.Get("/files", reqToken(), packages.ListPackageFiles) + m.Group("/{type}/{name}", func() { + m.Group("/{version}", func() { + m.Get("", packages.GetPackage) + m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage) + m.Get("/files", packages.ListPackageFiles) + }) + + m.Post("/-/link/{repo_name}", reqPackageAccess(perm.AccessModeWrite), packages.LinkPackage) + m.Post("/-/unlink", reqPackageAccess(perm.AccessModeWrite), packages.UnlinkPackage) }) - m.Get("/", reqToken(), packages.ListPackages) - }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly()) + + m.Get("/", packages.ListPackages) + }, reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly()) // Organizations m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs) diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index b38aa13167..a98b5dbc69 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -4,11 +4,14 @@ package packages import ( + "errors" "net/http" "code.gitea.io/gitea/models/packages" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/optional" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" @@ -213,3 +216,122 @@ func ListPackageFiles(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiPackageFiles) } + +// LinkPackage sets a repository link for a package +func LinkPackage(ctx *context.APIContext) { + // swagger:operation POST /packages/{owner}/{type}/{name}/-/link/{repo_name} package linkPackage + // --- + // summary: Link a package to a repository + // parameters: + // - name: owner + // in: path + // description: owner of the package + // type: string + // required: true + // - name: type + // in: path + // description: type of the package + // type: string + // required: true + // - name: name + // in: path + // description: name of the package + // type: string + // required: true + // - name: repo_name + // in: path + // description: name of the repository to link. + // type: string + // required: true + // responses: + // "201": + // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" + + pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParamRaw("type")), ctx.PathParamRaw("name")) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetPackageByName", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetPackageByName", err) + } + return + } + + repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParamRaw("repo_name")) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetRepositoryByName", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err) + } + return + } + + err = packages_service.LinkToRepository(ctx, pkg, repo, ctx.Doer) + if err != nil { + switch { + case errors.Is(err, util.ErrInvalidArgument): + ctx.Error(http.StatusBadRequest, "LinkToRepository", err) + case errors.Is(err, util.ErrPermissionDenied): + ctx.Error(http.StatusForbidden, "LinkToRepository", err) + default: + ctx.Error(http.StatusInternalServerError, "LinkToRepository", err) + } + return + } + ctx.Status(http.StatusCreated) +} + +// UnlinkPackage sets a repository link for a package +func UnlinkPackage(ctx *context.APIContext) { + // swagger:operation POST /packages/{owner}/{type}/{name}/-/unlink package unlinkPackage + // --- + // summary: Unlink a package from a repository + // parameters: + // - name: owner + // in: path + // description: owner of the package + // type: string + // required: true + // - name: type + // in: path + // description: type of the package + // type: string + // required: true + // - name: name + // in: path + // description: name of the package + // type: string + // required: true + // responses: + // "201": + // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" + + pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParamRaw("type")), ctx.PathParamRaw("name")) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetPackageByName", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetPackageByName", err) + } + return + } + + err = packages_service.UnlinkFromRepository(ctx, pkg, ctx.Doer) + if err != nil { + switch { + case errors.Is(err, util.ErrPermissionDenied): + ctx.Error(http.StatusForbidden, "UnlinkFromRepository", err) + case errors.Is(err, util.ErrInvalidArgument): + ctx.Error(http.StatusBadRequest, "UnlinkFromRepository", err) + default: + ctx.Error(http.StatusInternalServerError, "UnlinkFromRepository", err) + } + return + } + ctx.Status(http.StatusNoContent) +} diff --git a/services/packages/package_update.go b/services/packages/package_update.go new file mode 100644 index 0000000000..8d851fac53 --- /dev/null +++ b/services/packages/package_update.go @@ -0,0 +1,78 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package packages + +import ( + "context" + "fmt" + + org_model "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/util" +) + +func LinkToRepository(ctx context.Context, pkg *packages_model.Package, repo *repo_model.Repository, doer *user_model.User) error { + if pkg.OwnerID != repo.OwnerID { + return util.ErrPermissionDenied + } + if pkg.RepoID > 0 { + return util.ErrInvalidArgument + } + + perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) + if err != nil { + return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) + } + if !perms.CanWrite(unit.TypePackages) { + return util.ErrPermissionDenied + } + + if err := packages_model.SetRepositoryLink(ctx, pkg.ID, repo.ID); err != nil { + return fmt.Errorf("error while linking package '%v' to repo '%v' : %w", pkg.Name, repo.FullName(), err) + } + return nil +} + +func UnlinkFromRepository(ctx context.Context, pkg *packages_model.Package, doer *user_model.User) error { + if pkg.RepoID == 0 { + return util.ErrInvalidArgument + } + + repo, err := repo_model.GetRepositoryByID(ctx, pkg.RepoID) + if err != nil { + return fmt.Errorf("error getting repository %d: %w", pkg.RepoID, err) + } + + perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) + if err != nil { + return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) + } + if !perms.CanWrite(unit.TypePackages) { + return util.ErrPermissionDenied + } + + user, err := user_model.GetUserByID(ctx, pkg.OwnerID) + if err != nil { + return err + } + if !doer.IsAdmin { + if !user.IsOrganization() { + if doer.ID != pkg.OwnerID { + return fmt.Errorf("no permission to unlink package '%v' from its repository, or packages are disabled", pkg.Name) + } + } else { + isOrgAdmin, err := org_model.OrgFromUser(user).IsOrgAdmin(ctx, doer.ID) + if err != nil { + return err + } else if !isOrgAdmin { + return fmt.Errorf("no permission to unlink package '%v' from its repository, or packages are disabled", pkg.Name) + } + } + } + return packages_model.UnlinkRepository(ctx, pkg.ID) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index cfe6ae7656..7971be6620 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -4159,6 +4159,93 @@ } } }, + "/packages/{owner}/{type}/{name}/-/link/{repo_name}": { + "post": { + "tags": [ + "package" + ], + "summary": "Link a package to a repository", + "operationId": "linkPackage", + "parameters": [ + { + "type": "string", + "description": "owner of the package", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "type of the package", + "name": "type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the package", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository to link.", + "name": "repo_name", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "$ref": "#/responses/empty" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/packages/{owner}/{type}/{name}/-/unlink": { + "post": { + "tags": [ + "package" + ], + "summary": "Unlink a package from a repository", + "operationId": "unlinkPackage", + "parameters": [ + { + "type": "string", + "description": "owner of the package", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "type of the package", + "name": "type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the package", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "$ref": "#/responses/empty" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/packages/{owner}/{type}/{name}/{version}": { "get": { "produces": [ diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 27aed0feb1..c74f40a689 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" container_model "code.gitea.io/gitea/models/packages/container" + unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" @@ -35,7 +36,7 @@ func TestPackageAPI(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) session := loginUser(t, user.Name) tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) - tokenDeletePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage) + tokenWritePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage) packageName := "test-package" packageVersion := "1.0.3" @@ -99,8 +100,13 @@ func TestPackageAPI(t *testing.T) { DecodeJSON(t, resp, &ap1) assert.Nil(t, ap1.Repository) + // create a repository + repo, _, f := tests.CreateDeclarativeRepo(t, user, "", []unit_model.Type{unit_model.TypeCode}, nil, nil) + defer f() + // link to public repository - require.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 1)) + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, repo.Name)).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusCreated) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). AddTokenAuth(tokenReadPackage) @@ -109,10 +115,15 @@ func TestPackageAPI(t *testing.T) { var ap2 *api.Package DecodeJSON(t, resp, &ap2) assert.NotNil(t, ap2.Repository) - assert.EqualValues(t, 1, ap2.Repository.ID) + assert.EqualValues(t, repo.ID, ap2.Repository.ID) - // link to private repository - require.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 2)) + // link to repository without write access, should fail + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, "repo3")).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNotFound) + + // remove link + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/unlink", user.Name, packageName)).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNoContent) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). AddTokenAuth(tokenReadPackage) @@ -122,7 +133,18 @@ func TestPackageAPI(t *testing.T) { DecodeJSON(t, resp, &ap3) assert.Nil(t, ap3.Repository) - require.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, 2)) + // force link to a repository the currently logged-in user doesn't have access to + privateRepoID := int64(6) + require.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, privateRepoID)) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).AddTokenAuth(tokenReadPackage) + resp = MakeRequest(t, req, http.StatusOK) + + var ap4 *api.Package + DecodeJSON(t, resp, &ap4) + assert.Nil(t, ap4.Repository) + + require.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, privateRepoID)) }) }) @@ -153,11 +175,11 @@ func TestPackageAPI(t *testing.T) { defer tests.PrintCurrentTest(t)() req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenDeletePackage) + AddTokenAuth(tokenWritePackage) MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenDeletePackage) + AddTokenAuth(tokenWritePackage) MakeRequest(t, req, http.StatusNoContent) }) } From 12488d3f2da782c941d8f02f814c722b8fe41c7e Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 18 Feb 2025 19:29:08 +0800 Subject: [PATCH 13/79] Fix Untranslated Text on Actions Page (#33635) Fix the problem of untranslated text on the actions page Co-authored-by: wxiaoguang (cherry picked from commit ce65613690e4564d9961f847ebd6eb2137f0c885) --- models/actions/run_list.go | 5 +++-- routers/web/repo/actions/actions.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/models/actions/run_list.go b/models/actions/run_list.go index 4046c7d369..b9b9324e07 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/translation" webhook_module "code.gitea.io/gitea/modules/webhook" "xorm.io/builder" @@ -112,14 +113,14 @@ type StatusInfo struct { } // GetStatusInfoList returns a slice of StatusInfo -func GetStatusInfoList(ctx context.Context) []StatusInfo { +func GetStatusInfoList(ctx context.Context, lang translation.Locale) []StatusInfo { // same as those in aggregateJobStatus allStatus := []Status{StatusSuccess, StatusFailure, StatusWaiting, StatusRunning} statusInfoList := make([]StatusInfo, 0, 4) for _, s := range allStatus { statusInfoList = append(statusInfoList, StatusInfo{ Status: int(s), - DisplayedStatus: s.String(), + DisplayedStatus: s.LocaleString(lang), }) } return statusInfoList diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index e5134c1f62..e0ef709ea6 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -240,7 +240,7 @@ func List(ctx *context.Context) { } ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors) - ctx.Data["StatusInfoList"] = actions_model.GetStatusInfoList(ctx) + ctx.Data["StatusInfoList"] = actions_model.GetStatusInfoList(ctx, ctx.Locale) pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5) pager.SetDefaultParams(ctx) From fae5681858d510964f3444dc3fd54c04788a0a67 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2025 12:39:21 -0800 Subject: [PATCH 14/79] Fix omitempty bug (#33663) Fix #33660 (cherry picked from commit a25081f3801551457fe8ac10eb2817ceca21fbae) --- models/admin/task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/admin/task.go b/models/admin/task.go index c8bc95f981..5c2f7af49c 100644 --- a/models/admin/task.go +++ b/models/admin/task.go @@ -44,7 +44,7 @@ func init() { // TranslatableMessage represents JSON struct that can be translated with a Locale type TranslatableMessage struct { Format string - Args []any `json:"omitempty"` + Args []any `json:",omitempty"` } // LoadRepo loads repository of the task From e69be4cf5575c6e9ce1aada30212daf33cf51b40 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Feb 2025 20:17:56 -0800 Subject: [PATCH 15/79] Deleting repository should unlink all related packages (#33653) Fix #33634 --------- Co-authored-by: Giteabot Co-authored-by: wxiaoguang (cherry picked from commit f2fbb897f3bf68a1af1410a2b4ce7a289ef73c1a) --- services/repository/delete.go | 11 +++++++++++ services/repository/repository.go | 12 +----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/services/repository/delete.go b/services/repository/delete.go index 6e84194750..09213e5c65 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -16,6 +16,7 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" access_model "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" @@ -28,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" + federation_service "code.gitea.io/gitea/services/federation" "xorm.io/builder" ) @@ -289,6 +291,15 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID return err } + if err := federation_service.DeleteFollowingRepos(ctx, repo.ID); err != nil { + return err + } + + // unlink packages linked to this repository + if err = packages_model.UnlinkRepositoryFromAllPackages(ctx, repoID); err != nil { + return err + } + if err = committer.Commit(); err != nil { return err } diff --git a/services/repository/repository.go b/services/repository/repository.go index 116e24151b..35bcdfd528 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" - packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" @@ -22,7 +21,6 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - federation_service "code.gitea.io/gitea/services/federation" notify_service "code.gitea.io/gitea/services/notify" pull_service "code.gitea.io/gitea/services/pull" ) @@ -64,15 +62,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod notify_service.DeleteRepository(ctx, doer, repo) } - if err := DeleteRepositoryDirectly(ctx, doer, repo.ID); err != nil { - return err - } - - if err := federation_service.DeleteFollowingRepos(ctx, repo.ID); err != nil { - return err - } - - return packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID) + return DeleteRepositoryDirectly(ctx, doer, repo.ID) } // PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace From 77a1af5ab837ad31a4427f9f5fb9718953f59c46 Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 26 Feb 2025 14:36:53 +0000 Subject: [PATCH 16/79] feat(ui): add quota overview (#6602) Add UI to the quota feature to see what quotas applies to you and if you're exceeding any quota, it's designed to be a general size overview although it's exclusively filled with quota features for now. There's also no UI to see what item is actually taking in the most size. Purely an quota overview. Screenshots: ![](https://codeberg.org/attachments/9f7480f2-4c31-4d70-8aec-61db79282a1e) ![](https://codeberg.org/attachments/0bd45bf3-28c5-47bf-8fff-c4ae9f38cb28) With inspiration from concept by 0ko: ![](https://codeberg.org/attachments/b8154a52-6fba-42fc-a4a8-b3ab1527fb33) Co-authored-by: Otto Richter Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6602 Reviewed-by: Otto Co-authored-by: Gusted Co-committed-by: Gusted --- models/quota/default.go | 2 +- models/quota/group.go | 31 ++++--- models/quota/quota.go | 3 +- models/quota/quota_group_test.go | 37 +++++--- models/quota/rule.go | 22 +++-- options/locale/locale_en-US.ini | 21 +++++ routers/web/org/setting/storage_overview.go | 20 +++++ routers/web/shared/storage_overview.go | 90 +++++++++++++++++++ routers/web/user/setting/storage_overview.go | 20 +++++ routers/web/web.go | 6 +- templates/org/settings/navbar.tmpl | 7 +- templates/org/settings/storage_overview.tmpl | 5 ++ templates/shared/quota_overview.tmpl | 32 +++++++ templates/user/settings/navbar.tmpl | 5 ++ templates/user/settings/storage_overview.tmpl | 5 ++ tests/e2e/e2e_test.go | 1 + tests/e2e/fixtures/attachment.yml | 12 +++ tests/e2e/fixtures/quota_group.yml | 1 + tests/e2e/fixtures/quota_group_mapping.yml | 5 ++ .../e2e/fixtures/quota_group_rule_mapping.yml | 14 +++ tests/e2e/fixtures/quota_rule.yml | 13 +++ tests/e2e/fixtures/repository.yml | 12 +++ tests/e2e/user-settings.test.e2e.ts | 9 ++ web_src/css/repo.css | 8 ++ 24 files changed, 348 insertions(+), 33 deletions(-) create mode 100644 routers/web/org/setting/storage_overview.go create mode 100644 routers/web/shared/storage_overview.go create mode 100644 routers/web/user/setting/storage_overview.go create mode 100644 templates/org/settings/storage_overview.tmpl create mode 100644 templates/shared/quota_overview.tmpl create mode 100644 templates/user/settings/storage_overview.tmpl create mode 100644 tests/e2e/fixtures/attachment.yml create mode 100644 tests/e2e/fixtures/quota_group.yml create mode 100644 tests/e2e/fixtures/quota_group_mapping.yml create mode 100644 tests/e2e/fixtures/quota_group_rule_mapping.yml create mode 100644 tests/e2e/fixtures/quota_rule.yml create mode 100644 tests/e2e/fixtures/repository.yml diff --git a/models/quota/default.go b/models/quota/default.go index 6b553d6f71..e53e47bade 100644 --- a/models/quota/default.go +++ b/models/quota/default.go @@ -7,7 +7,7 @@ import ( "code.gitea.io/gitea/modules/setting" ) -func EvaluateDefault(used Used, forSubject LimitSubject) bool { +func EvaluateDefault(used Used, forSubject LimitSubject) (bool, int64) { groups := GroupList{ &Group{ Name: "builtin-default-group", diff --git a/models/quota/group.go b/models/quota/group.go index 0acb5b255e..50080748a1 100644 --- a/models/quota/group.go +++ b/models/quota/group.go @@ -5,6 +5,7 @@ package quota import ( "context" + "math" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -199,15 +200,20 @@ var affectsMap = map[LimitSubject]LimitSubjects{ }, } -func (g *Group) Evaluate(used Used, forSubject LimitSubject) (bool, bool) { +// Evaluate returns whether the size used is acceptable for the topic if a rule +// was found, and returns the smallest limit of all applicable rules or the +// first limit found to be unacceptable for the size used. +func (g *Group) Evaluate(used Used, forSubject LimitSubject) (bool, bool, int64) { var found bool + foundLimit := int64(math.MaxInt64) for _, rule := range g.Rules { ok, has := rule.Evaluate(used, forSubject) if has { - found = true if !ok { - return false, true + return false, true, rule.Limit } + found = true + foundLimit = min(foundLimit, rule.Limit) } } @@ -216,32 +222,35 @@ func (g *Group) Evaluate(used Used, forSubject LimitSubject) (bool, bool) { // subjects below for _, subject := range affectsMap[forSubject] { - ok, has := g.Evaluate(used, subject) + ok, has, limit := g.Evaluate(used, subject) if has { - found = true if !ok { - return false, true + return false, true, limit } + found = true + foundLimit = min(foundLimit, limit) } } } - return true, found + return true, found, foundLimit } -func (gl *GroupList) Evaluate(used Used, forSubject LimitSubject) bool { +// Evaluate returns if the used size is acceptable for the subject and the +// lowest limit that is acceptable for the subject. +func (gl *GroupList) Evaluate(used Used, forSubject LimitSubject) (bool, int64) { // If there are no groups, use the configured defaults: if gl == nil || len(*gl) == 0 { return EvaluateDefault(used, forSubject) } for _, group := range *gl { - ok, has := group.Evaluate(used, forSubject) + ok, has, limit := group.Evaluate(used, forSubject) if has && ok { - return true + return true, limit } } - return false + return false, 0 } func GetGroupByName(ctx context.Context, name string) (*Group, error) { diff --git a/models/quota/quota.go b/models/quota/quota.go index d38bfab3cc..ee3fec6c1a 100644 --- a/models/quota/quota.go +++ b/models/quota/quota.go @@ -32,5 +32,6 @@ func EvaluateForUser(ctx context.Context, userID int64, subject LimitSubject) (b return false, err } - return groups.Evaluate(*used, subject), nil + acceptable, _ := groups.Evaluate(*used, subject) + return acceptable, nil } diff --git a/models/quota/quota_group_test.go b/models/quota/quota_group_test.go index bc258588f9..edbf43fcf5 100644 --- a/models/quota/quota_group_test.go +++ b/models/quota/quota_group_test.go @@ -4,6 +4,7 @@ package quota_test import ( + "math" "testing" quota_model "code.gitea.io/gitea/models/quota" @@ -36,9 +37,10 @@ func TestQuotaGroupAllRulesMustPass(t *testing.T) { // Within a group, *all* rules must pass. Thus, if we have a deny-all rule, // and an unlimited rule, that will always fail. - ok, has := group.Evaluate(used, quota_model.LimitSubjectSizeAll) + ok, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeAll) assert.True(t, has) assert.False(t, ok) + assert.EqualValues(t, 0, limit) } func TestQuotaGroupRuleScenario1(t *testing.T) { @@ -66,21 +68,25 @@ func TestQuotaGroupRuleScenario1(t *testing.T) { used.Size.Assets.Packages.All = 256 used.Size.Git.LFS = 16 - ok, has := group.Evaluate(used, quota_model.LimitSubjectSizeAssetsAttachmentsReleases) + ok, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeAssetsAttachmentsReleases) assert.True(t, has, "size:assets:attachments:releases is covered") assert.True(t, ok, "size:assets:attachments:releases passes") + assert.EqualValues(t, 1024, limit) - ok, has = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) + ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) assert.True(t, has, "size:assets:packages:all is covered") assert.True(t, ok, "size:assets:packages:all passes") + assert.EqualValues(t, 1024, limit) - ok, has = group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) + ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) assert.True(t, has, "size:git:lfs is covered") assert.False(t, ok, "size:git:lfs fails") + assert.EqualValues(t, 0, limit) - ok, has = group.Evaluate(used, quota_model.LimitSubjectSizeAll) + ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAll) assert.True(t, has, "size:all is covered") assert.False(t, ok, "size:all fails") + assert.EqualValues(t, 0, limit) } func TestQuotaGroupRuleCombination(t *testing.T) { @@ -109,23 +115,27 @@ func TestQuotaGroupRuleCombination(t *testing.T) { } // Git LFS isn't covered by any rule - _, has := group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) + _, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) assert.False(t, has) + assert.EqualValues(t, math.MaxInt, limit) // repos:all is covered, and is passing - ok, has := group.Evaluate(used, quota_model.LimitSubjectSizeReposAll) + ok, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeReposAll) assert.True(t, has) assert.True(t, ok) + assert.EqualValues(t, 4096, limit) // packages:all is covered, and is failing - ok, has = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) + ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) assert.True(t, has) assert.False(t, ok) + assert.EqualValues(t, 0, limit) // size:all is covered, and is failing (due to packages:all being over quota) - ok, has = group.Evaluate(used, quota_model.LimitSubjectSizeAll) + ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAll) assert.True(t, has, "size:all should be covered") assert.False(t, ok, "size:all should fail") + assert.EqualValues(t, 0, limit) } func TestQuotaGroupListsRequireOnlyOnePassing(t *testing.T) { @@ -159,8 +169,9 @@ func TestQuotaGroupListsRequireOnlyOnePassing(t *testing.T) { used.Size.Repos.Public = 1024 // In a group list, if any group passes, the entire evaluation passes. - ok := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) + ok, limit := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) assert.True(t, ok) + assert.EqualValues(t, -1, limit) } func TestQuotaGroupListAllFailing(t *testing.T) { @@ -193,8 +204,9 @@ func TestQuotaGroupListAllFailing(t *testing.T) { used := quota_model.Used{} used.Size.Repos.Public = 2048 - ok := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) + ok, limit := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) assert.False(t, ok) + assert.EqualValues(t, 0, limit) } func TestQuotaGroupListEmpty(t *testing.T) { @@ -203,6 +215,7 @@ func TestQuotaGroupListEmpty(t *testing.T) { used := quota_model.Used{} used.Size.Repos.Public = 2048 - ok := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) + ok, limit := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) assert.True(t, ok) + assert.EqualValues(t, -1, limit) } diff --git a/models/quota/rule.go b/models/quota/rule.go index b0c6c0f4b6..cb23b74b52 100644 --- a/models/quota/rule.go +++ b/models/quota/rule.go @@ -20,6 +20,22 @@ func (r *Rule) TableName() string { return "quota_rule" } +func (r Rule) Acceptable(used Used) bool { + if r.Limit == -1 { + return true + } + + return r.Sum(used) <= r.Limit +} + +func (r Rule) Sum(used Used) int64 { + var sum int64 + for _, subject := range r.Subjects { + sum += used.CalculateFor(subject) + } + return sum +} + func (r Rule) Evaluate(used Used, forSubject LimitSubject) (bool, bool) { // If there's no limit, short circuit out if r.Limit == -1 { @@ -31,11 +47,7 @@ func (r Rule) Evaluate(used Used, forSubject LimitSubject) (bool, bool) { return false, false } - var sum int64 - for _, subject := range r.Subjects { - sum += used.CalculateFor(subject) - } - return sum <= r.Limit, true + return r.Sum(used) <= r.Limit, true } func (r *Rule) Edit(ctx context.Context, limit *int64, subjects *LimitSubjects) (*Rule, error) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 29c696f29f..22c726a793 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -749,6 +749,8 @@ organization = Organizations uid = UID webauthn = Two-factor authentication (Security keys) blocked_users = Blocked users +storage_overview = Storage overview +quota = Quota public_profile = Public profile biography_placeholder = Tell others a little bit about yourself! (Markdown is supported) @@ -1054,6 +1056,25 @@ user_unblock_success = The user has been unblocked successfully. user_block_success = The user has been blocked successfully. user_block_yourself = You cannot block yourself. +quota.applies_to_user = The following quota rules apply to your account +quota.applies_to_org = The following quota rules apply to this organisation +quota.rule.exceeded = Exceeded +quota.rule.exceeded.helper = The total size of objects for this rule has exceeded the quota. +quota.rule.no_limit = Unlimited +quota.sizes.all = All +quota.sizes.repos.all = Repositories +quota.sizes.repos.public = Public repositories +quota.sizes.repos.private = Private repositories +quota.sizes.git.all = Git content +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.all = Assets +quota.sizes.assets.attachments.all = Attachments +quota.sizes.assets.attachments.issues = Issue attachments +quota.sizes.assets.attachments.releases = Release attachments +quota.sizes.assets.artifacts = Artifacts +quota.sizes.assets.packages.all = Packages +quota.sizes.wiki = Wiki + [repo] rss.must_be_on_branch = You must be on a branch to have an RSS feed. diff --git a/routers/web/org/setting/storage_overview.go b/routers/web/org/setting/storage_overview.go new file mode 100644 index 0000000000..4b9bd02ca4 --- /dev/null +++ b/routers/web/org/setting/storage_overview.go @@ -0,0 +1,20 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package setting + +import ( + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/routers/web/shared" + "code.gitea.io/gitea/services/context" +) + +const ( + tplSettingsStorageOverview base.TplName = "org/settings/storage_overview" +) + +// StorageOverview render a size overview of the organization, as well as relevant +// quota limits of the instance. +func StorageOverview(ctx *context.Context) { + shared.StorageOverview(ctx, ctx.Org.Organization.ID, tplSettingsStorageOverview) +} diff --git a/routers/web/shared/storage_overview.go b/routers/web/shared/storage_overview.go new file mode 100644 index 0000000000..3bebdfb688 --- /dev/null +++ b/routers/web/shared/storage_overview.go @@ -0,0 +1,90 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package shared + +import ( + "html/template" + "net/http" + + quota_model "code.gitea.io/gitea/models/quota" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/context" +) + +// StorageOverview render a size overview of the user, as well as relevant +// quota limits of the instance. +func StorageOverview(ctx *context.Context, userID int64, tpl base.TplName) { + if !setting.Quota.Enabled { + ctx.NotFound("MustEnableQuota", nil) + } + ctx.Data["Title"] = ctx.Tr("settings.storage_overview") + ctx.Data["PageIsStorageOverview"] = true + + ctx.Data["Color"] = func(subject quota_model.LimitSubject) float64 { + return float64(subject) * 137.50776405003785 // Golden angle. + } + + ctx.Data["PrettySubject"] = func(subject quota_model.LimitSubject) template.HTML { + switch subject { + case quota_model.LimitSubjectSizeAll: + return ctx.Locale.Tr("settings.quota.sizes.all") + case quota_model.LimitSubjectSizeReposAll: + return ctx.Locale.Tr("settings.quota.sizes.repos.all") + case quota_model.LimitSubjectSizeReposPublic: + return ctx.Locale.Tr("settings.quota.sizes.repos.public") + case quota_model.LimitSubjectSizeReposPrivate: + return ctx.Locale.Tr("settings.quota.sizes.repos.private") + case quota_model.LimitSubjectSizeGitAll: + return ctx.Locale.Tr("settings.quota.sizes.git.all") + case quota_model.LimitSubjectSizeGitLFS: + return ctx.Locale.Tr("settings.quota.sizes.git.lfs") + case quota_model.LimitSubjectSizeAssetsAll: + return ctx.Locale.Tr("settings.quota.sizes.assets.all") + case quota_model.LimitSubjectSizeAssetsAttachmentsAll: + return ctx.Locale.Tr("settings.quota.sizes.assets.attachments.all") + case quota_model.LimitSubjectSizeAssetsAttachmentsIssues: + return ctx.Locale.Tr("settings.quota.sizes.assets.attachments.issues") + case quota_model.LimitSubjectSizeAssetsAttachmentsReleases: + return ctx.Locale.Tr("settings.quota.sizes.assets.attachments.releases") + case quota_model.LimitSubjectSizeAssetsArtifacts: + return ctx.Locale.Tr("settings.quota.sizes.assets.artifacts") + case quota_model.LimitSubjectSizeAssetsPackagesAll: + return ctx.Locale.Tr("settings.quota.sizes.assets.packages.all") + case quota_model.LimitSubjectSizeWiki: + return ctx.Locale.Tr("settings.quota.sizes.wiki") + default: + panic("unrecognized subject: " + subject.String()) + } + } + + sizeUsed, err := quota_model.GetUsedForUser(ctx, userID) + if err != nil { + ctx.ServerError("GetUsedForUser", err) + return + } + ctx.Data["SizeUsed"] = sizeUsed + + quotaGroups, err := quota_model.GetGroupsForUser(ctx, userID) + if err != nil { + ctx.ServerError("GetGroupsForUser", err) + return + } + if len(quotaGroups) == 0 { + quotaGroups = append(quotaGroups, "a_model.Group{ + Name: "Global quota", + Rules: []quota_model.Rule{ + { + Name: "Default", + Limit: setting.Quota.Default.Total, + Subjects: quota_model.LimitSubjects{quota_model.LimitSubjectSizeAll}, + }, + }, + }, + ) + } + ctx.Data["QuotaGroups"] = quotaGroups + + ctx.HTML(http.StatusOK, tpl) +} diff --git a/routers/web/user/setting/storage_overview.go b/routers/web/user/setting/storage_overview.go new file mode 100644 index 0000000000..8a0c773077 --- /dev/null +++ b/routers/web/user/setting/storage_overview.go @@ -0,0 +1,20 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package setting + +import ( + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/routers/web/shared" + "code.gitea.io/gitea/services/context" +) + +const ( + tplSettingsStorageOverview base.TplName = "user/settings/storage_overview" +) + +// StorageOverview render a size overview of the user, as well as relevant +// quota limits of the instance. +func StorageOverview(ctx *context.Context) { + shared.StorageOverview(ctx, ctx.Doer.ID, tplSettingsStorageOverview) +} diff --git a/routers/web/web.go b/routers/web/web.go index 4d8d280c89..a519ff74c1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -644,7 +644,8 @@ func registerRoutes(m *web.Route) { m.Get("", user_setting.BlockedUsers) m.Post("/unblock", user_setting.UnblockUser) }) - }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled)) + m.Get("/storage_overview", user_setting.StorageOverview) + }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled, "EnableQuota", setting.Quota.Enabled)) m.Group("/user", func() { m.Get("/activate", auth.Activate) @@ -930,6 +931,7 @@ func registerRoutes(m *web.Route) { m.Post("/block", org_setting.BlockedUsersBlock) m.Post("/unblock", org_setting.BlockedUsersUnblock) }) + m.Get("/storage_overview", org_setting.StorageOverview) m.Group("/packages", func() { m.Get("", org.Packages) @@ -949,7 +951,7 @@ func registerRoutes(m *web.Route) { m.Post("/rebuild", org.RebuildCargoIndex) }) }, packagesEnabled) - }, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true)) + }, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "EnableQuota", setting.Quota.Enabled, "PageIsOrgSettings", true)) }, context.OrgAssignment(true, true)) }, reqSignIn) // ***** END: Organization ***** diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl index b245768203..4ae7f56aca 100644 --- a/templates/org/settings/navbar.tmpl +++ b/templates/org/settings/navbar.tmpl @@ -38,9 +38,14 @@
{{end}} - + {{ctx.Locale.Tr "settings.blocked_users"}} + {{if .EnableQuota}} + + {{ctx.Locale.Tr "settings.storage_overview"}} + + {{end}} {{ctx.Locale.Tr "org.settings.delete"}} diff --git a/templates/org/settings/storage_overview.tmpl b/templates/org/settings/storage_overview.tmpl new file mode 100644 index 0000000000..e3f8c53152 --- /dev/null +++ b/templates/org/settings/storage_overview.tmpl @@ -0,0 +1,5 @@ +{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings size-overview")}} +
+ {{template "shared/quota_overview" .}} +
+{{template "org/settings/layout_footer" .}} diff --git a/templates/shared/quota_overview.tmpl b/templates/shared/quota_overview.tmpl new file mode 100644 index 0000000000..cb8b57d724 --- /dev/null +++ b/templates/shared/quota_overview.tmpl @@ -0,0 +1,32 @@ +

+ {{ctx.Locale.Tr "settings.quota"}} +

+
+

{{if .ContextUser.IsOrganization}}{{ctx.Locale.Tr "settings.quota.applies_to_org"}}{{else}}{{ctx.Locale.Tr "settings.quota.applies_to_user"}}{{end}}:

+ {{range $group := .QuotaGroups}} +

{{$group.Name}}

+
+ {{range $rule := .Rules}} +
+ + {{if $rule.Acceptable ($.SizeUsed)}} + {{svg "octicon-check-circle-fill" 16 "text green"}} + {{$rule.Name}} + {{else}} + {{svg "octicon-alert-fill" 16 "text red"}} + + {{$rule.Name}} – {{ctx.Locale.Tr "settings.quota.rule.exceeded"}} + + {{end}} + + {{ctx.Locale.TrSize ($rule.Sum $.SizeUsed)}} / {{if eq $rule.Limit -1 -}}{{ctx.Locale.Tr "settings.quota.rule.no_limit"}}{{else}}{{ctx.Locale.TrSize $rule.Limit}}{{end}} +
+
+ {{range $idx, $subject := .Subjects}} +
+ {{end}} +
+ {{end}} +
+ {{end}} +
diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl index d45d89ee9f..eadf2ee9e5 100644 --- a/templates/user/settings/navbar.tmpl +++ b/templates/user/settings/navbar.tmpl @@ -51,6 +51,11 @@ {{ctx.Locale.Tr "settings.repos"}} + {{if .EnableQuota}} + + {{ctx.Locale.Tr "settings.storage_overview"}} + + {{end}} {{ctx.Locale.Tr "settings.blocked_users"}} diff --git a/templates/user/settings/storage_overview.tmpl b/templates/user/settings/storage_overview.tmpl new file mode 100644 index 0000000000..daa0cbd913 --- /dev/null +++ b/templates/user/settings/storage_overview.tmpl @@ -0,0 +1,5 @@ +{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings size-overview")}} +
+ {{template "shared/quota_overview" .}} +
+{{template "user/settings/layout_footer" .}} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 8e4a9653cd..aecb2c70c7 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -37,6 +37,7 @@ func TestMain(m *testing.M) { defer cancel() tests.InitTest(true) + setting.Quota.Enabled = true initChangedFiles() testE2eWebRoutes = routers.NormalRoutes() diff --git a/tests/e2e/fixtures/attachment.yml b/tests/e2e/fixtures/attachment.yml new file mode 100644 index 0000000000..26069b95c1 --- /dev/null +++ b/tests/e2e/fixtures/attachment.yml @@ -0,0 +1,12 @@ +- + id: 1001 + uuid: 5792c349-8a26-46b8-b4cd-2c290f118285 + repo_id: 1 + issue_id: 1 + release_id: 0 + uploader_id: 3 + comment_id: 1 + name: forgejo-secrets.txt + download_count: 0 + size: 536870911 + created_unix: 1730000000 diff --git a/tests/e2e/fixtures/quota_group.yml b/tests/e2e/fixtures/quota_group.yml new file mode 100644 index 0000000000..0d04ba6767 --- /dev/null +++ b/tests/e2e/fixtures/quota_group.yml @@ -0,0 +1 @@ +- name: trusted-user diff --git a/tests/e2e/fixtures/quota_group_mapping.yml b/tests/e2e/fixtures/quota_group_mapping.yml new file mode 100644 index 0000000000..8339569185 --- /dev/null +++ b/tests/e2e/fixtures/quota_group_mapping.yml @@ -0,0 +1,5 @@ +- + id: 1001 + kind: 0 + mapped_id: 2 + group_name: trusted-user diff --git a/tests/e2e/fixtures/quota_group_rule_mapping.yml b/tests/e2e/fixtures/quota_group_rule_mapping.yml new file mode 100644 index 0000000000..1144888f21 --- /dev/null +++ b/tests/e2e/fixtures/quota_group_rule_mapping.yml @@ -0,0 +1,14 @@ +- + id: 1001 + group_name: trusted-user + rule_name: git-lfs + +- + id: 1002 + group_name: trusted-user + rule_name: "all:assets" + +- + id: 1003 + group_name: trusted-user + rule_name: "Multi subjects" diff --git a/tests/e2e/fixtures/quota_rule.yml b/tests/e2e/fixtures/quota_rule.yml new file mode 100644 index 0000000000..f6170cbdaa --- /dev/null +++ b/tests/e2e/fixtures/quota_rule.yml @@ -0,0 +1,13 @@ +- + name: git-lfs + limit: 512 + subjects: [6] + +- + name: "all:assets" + limit: -1 + subjects: [7] + +- name: "Multi subjects" + limit: 5000000000 + subjects: [8,6] diff --git a/tests/e2e/fixtures/repository.yml b/tests/e2e/fixtures/repository.yml new file mode 100644 index 0000000000..ac87721d6a --- /dev/null +++ b/tests/e2e/fixtures/repository.yml @@ -0,0 +1,12 @@ +- + id: 1001 + owner_id: 2 + owner_name: user2 + lower_name: large-lfs + name: large-lfs + default_branch: main + is_empty: false + is_archived: false + is_private: true + status: 0 + lfs_size: 8192 diff --git a/tests/e2e/user-settings.test.e2e.ts b/tests/e2e/user-settings.test.e2e.ts index b36a17b5b2..b2b34c263b 100644 --- a/tests/e2e/user-settings.test.e2e.ts +++ b/tests/e2e/user-settings.test.e2e.ts @@ -63,3 +63,12 @@ test('User: Profile settings', async ({browser}, workerInfo) => { await page.goto('/user2?tab=activity'); await expect(page.getByText('Your activity is visible to everyone')).toBeVisible(); }); + +test('User: Storage overview', async ({browser}, workerInfo) => { + const page = await login({browser}, workerInfo); + await page.goto('/user/settings/storage_overview'); + await page.waitForLoadState(); + await page.getByLabel('Git LFS – 8 KiB').nth(1).hover({position: {x: 250, y: 2}}); + await expect(page.getByText('Git LFS')).toBeVisible(); + await save_visual(page); +}); diff --git a/web_src/css/repo.css b/web_src/css/repo.css index a9720ec6da..46c6b70c29 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -2035,6 +2035,14 @@ details.repo-search-result summary::marker { overflow: hidden; } +.size-overview .segment:has(> .bar) { + display: flex; + height: 10px; + padding: 0; + border-radius: 3px; + overflow: hidden; +} + #cite-repo-modal #citation-panel { display: flex; width: 100%; From 2024031a7a9e7b4f61e38676c5285a588f952f7b Mon Sep 17 00:00:00 2001 From: mhughes9 Date: Wed, 26 Feb 2025 18:46:51 +0000 Subject: [PATCH 17/79] Simplify pronouns in user settings (#6835) The main change here is to use `datalist` for pronouns This supports (see also docs[1]): * Displaying the value already set by the user (if any), otherwise * Presenting a list of common options to the user, and * Allowing them to freely enter any value This setup requires no additional JS and resolves[2]. This is different from the previous flow which used, if JS was available: * A menu for a default 'recognised' set of pronouns, and if the user wanted another value: * An extra text div if the user wanted to enter custom pronouns Without JS enabled both the menu and the custom text div would always be displayed. This change means there's no longer a distinction between 'custom' and 'recognised' pronouns (this difference looks to have only been made in code, and not in any data models). Link: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist [1] Link: https://codeberg.org/forgejo/forgejo/issues/6774 [2] Co-authored-by: Matthew Hughes Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6835 Reviewed-by: Gusted Reviewed-by: Otto Co-authored-by: mhughes9 Co-committed-by: mhughes9 --- options/locale/locale_en-US.ini | 2 -- routers/web/user/setting/profile.go | 8 ++--- templates/user/settings/profile.tmpl | 38 ++++----------------- tests/e2e/user-settings.test.e2e.ts | 16 ++++++--- web_src/js/features/user-settings.js | 50 ---------------------------- web_src/js/index.js | 2 -- 6 files changed, 21 insertions(+), 95 deletions(-) delete mode 100644 web_src/js/features/user-settings.js diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 22c726a793..dfae2b7b6f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -761,8 +761,6 @@ full_name = Full name website = Website location = Location pronouns = Pronouns -pronouns_custom = Custom -pronouns_custom_label = Custom pronouns pronouns_unspecified = Unspecified update_theme = Change theme update_profile = Update profile diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 33b2919d69..7d6a678c60 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -12,7 +12,6 @@ import ( "net/http" "os" "path/filepath" - "slices" "strings" "time" @@ -42,8 +41,7 @@ const ( tplSettingsRepositories base.TplName = "user/settings/repos" ) -// must be kept in sync with `web_src/js/features/user-settings.js` -var recognisedPronouns = []string{"", "he/him", "she/her", "they/them", "it/its", "any pronouns"} +var commonPronouns = []string{"he/him", "she/her", "they/them", "it/its", "any pronouns"} // Profile render user's profile page func Profile(ctx *context.Context) { @@ -51,8 +49,8 @@ func Profile(ctx *context.Context) { ctx.Data["PageIsSettingsProfile"] = true ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) - ctx.Data["PronounsAreCustom"] = !slices.Contains(recognisedPronouns, ctx.Doer.Pronouns) ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod + ctx.Data["CommonPronouns"] = commonPronouns ctx.HTML(http.StatusOK, tplSettingsProfile) } @@ -63,8 +61,8 @@ func ProfilePost(ctx *context.Context) { ctx.Data["PageIsSettingsProfile"] = true ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) - ctx.Data["PronounsAreCustom"] = !slices.Contains(recognisedPronouns, ctx.Doer.Pronouns) ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod + ctx.Data["CommonPronouns"] = commonPronouns if ctx.HasError() { ctx.HTML(http.StatusOK, tplSettingsProfile) diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 503a977158..3bc8800a76 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -30,38 +30,14 @@ -
+ {{else if eq .Type 38}} +
+ {{svg "octicon-list-unordered" 16}} + {{template "shared/user/avatarlink" dict "user" .Poster}} + + + {{template "shared/user/authorlink" .Poster}} + {{$createdStr}} + +
    + + + {{if and .Aggregator.PrevClosed (not .Aggregator.IsClosed)}} +
  • + {{svg "octicon-dot-fill"}} + {{if .Issue.IsPull}} + {{ctx.Locale.Tr "repo.pulls.reopened_at" "" ""}} + {{else}} + {{ctx.Locale.Tr "repo.issues.reopened_at" "" ""}} + {{end}} +
  • + {{else if and (not .Aggregator.PrevClosed) .Aggregator.IsClosed}} + {{svg "octicon-circle-slash"}} +
  • + {{if .Issue.IsPull}} + {{ctx.Locale.Tr "repo.pulls.closed_at" "" ""}} + {{else}} + {{ctx.Locale.Tr "repo.issues.closed_at" "" ""}} + {{end}} +
  • + {{end}} + + + {{if or .AddedLabels .RemovedLabels}} +
  • + {{svg "octicon-tag" 20}} + {{if and .AddedLabels (not .RemovedLabels)}} + {{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink .Issue.IsPull) ""}} + {{else if and (not .AddedLabels) .RemovedLabels}} + {{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink .Issue.IsPull) ""}} + {{else}} + {{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink .Issue.IsPull) (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink .Issue.IsPull) ""}} + {{end}} +
  • + {{end}} + + {{if or .AddedRequestReview .RemovedRequestReview}} +
  • + {{svg "octicon-tag" 20}} + + + {{if and (eq (len .RemovedRequestReview) 1) (eq (len .AddedRequestReview) 0) (eq ((index .RemovedRequestReview 0).ID) .PosterID) (eq ((index .RemovedRequestReview 0).Type) "user")}} + {{ctx.Locale.Tr "repo.issues.review.remove_review_request_self" ""}} + {{else if and .AddedRequestReview (not .RemovedRequestReview)}} + {{ctx.Locale.TrN (len .AddedRequestReview) "repo.issues.review.add_review_request" "repo.issues.review.add_review_requests" (RenderReviewRequest .AddedRequestReview) ""}} + {{else if and (not .AddedRequestReview) .RemovedRequestReview}} + {{ctx.Locale.TrN (len .RemovedRequestReview) "repo.issues.review.remove_review_request" "repo.issues.review.remove_review_requests" (RenderReviewRequest .RemovedRequestReview) ""}} + {{else}} + {{ctx.Locale.Tr "repo.issues.review.add_remove_review_requests" (RenderReviewRequest .AddedRequestReview) (RenderReviewRequest .RemovedRequestReview) ""}} + {{end}} + + +
  • + {{end}} +
+
+
{{end}} {{end}} {{end}} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 46c6b70c29..1b962aacb6 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -759,7 +759,7 @@ td .commit-summary { .repository.view.issue .comment-list .timeline-item, .repository.view.issue .comment-list .timeline-item-group { - padding: 16px 0; + padding: 0.65rem 0; } .repository.view.issue .comment-list .timeline-item-group .timeline-item { @@ -842,6 +842,18 @@ td .commit-summary { margin-right: 0.25em; } +.repository.view.issue .comment-list .timeline-item .aggregated-actions .badge { + width: 20px; + height: 20px; + margin-top: 5px; + padding: 12px; +} + +.repository.view.issue .comment-list .timeline-item .aggregated-actions .badge .svg { + width: 20px; + height: 20px; +} + .singular-commit { display: flex; align-items: center; From 4c123d8e3b160b8d53547a704f2b0361bd518ce0 Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 5 Mar 2025 18:40:13 +0100 Subject: [PATCH 51/79] feat: improve error handling of commit rendering - Simplify if-else expression to `NotFoundOrServerError`. - I cannot find an existing scenario where `Getdiff` returns an error and where it therefore should show a 404 error in the context of rendering a diff of a commit. So simply return it as an Internal Server Error, this also helps with debugging if an actual error occurs here (404 errors are only logged at the DEBUG level). - The first change is already covered under existing testing, the second change is not trivial to test. --- routers/web/repo/commit.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index c2436a1c59..857e34381e 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -315,11 +315,7 @@ func Diff(ctx *context.Context) { commit, err := gitRepo.GetCommit(commitID) if err != nil { - if git.IsErrNotExist(err) { - ctx.NotFound("Repo.GitRepo.GetCommit", err) - } else { - ctx.ServerError("Repo.GitRepo.GetCommit", err) - } + ctx.NotFoundOrServerError("gitRepo.GetCommit", git.IsErrNotExist, err) return } if len(commitID) != commit.ID.Type().FullLength() { @@ -343,7 +339,7 @@ func Diff(ctx *context.Context) { FileOnly: fileOnly, }, files...) if err != nil { - ctx.NotFound("GetDiff", err) + ctx.ServerError("GetDiff", err) return } From 19cd33d35b0574284aab78b57383549ca2202903 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 6 Mar 2025 00:42:02 +0000 Subject: [PATCH 52/79] Update module github.com/urfave/cli/v2 to v2.27.6 (forgejo) (#7132) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c6060e0008..26be560f28 100644 --- a/go.mod +++ b/go.mod @@ -94,7 +94,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.0 github.com/ulikunitz/xz v0.5.12 - github.com/urfave/cli/v2 v2.27.5 + github.com/urfave/cli/v2 v2.27.6 github.com/valyala/fastjson v1.6.4 github.com/yohcop/openid-go v1.0.1 github.com/yuin/goldmark v1.7.8 diff --git a/go.sum b/go.sum index 8b9c0d88d5..b8be9d1054 100644 --- a/go.sum +++ b/go.sum @@ -1412,8 +1412,8 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= -github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= +github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= From 5e08b51feda99ad2543bd794b6d456c68ac49064 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 6 Mar 2025 08:23:55 +0000 Subject: [PATCH 53/79] Update module golang.org/x/crypto to v0.36.0 (forgejo) (#7134) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7134 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 26be560f28..23282f5d9f 100644 --- a/go.mod +++ b/go.mod @@ -101,13 +101,13 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc gitlab.com/gitlab-org/api/client-go v0.123.0 go.uber.org/mock v0.4.0 - golang.org/x/crypto v0.35.0 + golang.org/x/crypto v0.36.0 golang.org/x/image v0.23.0 golang.org/x/net v0.36.0 golang.org/x/oauth2 v0.27.0 - golang.org/x/sync v0.11.0 - golang.org/x/sys v0.30.0 - golang.org/x/text v0.22.0 + golang.org/x/sync v0.12.0 + golang.org/x/sys v0.31.0 + golang.org/x/text v0.23.0 google.golang.org/grpc v1.70.0 google.golang.org/protobuf v1.36.3 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df diff --git a/go.sum b/go.sum index b8be9d1054..70c0161c07 100644 --- a/go.sum +++ b/go.sum @@ -1507,8 +1507,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1693,8 +1693,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1786,8 +1786,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1802,8 +1802,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1824,8 +1824,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 932201fa2367e7e16da23dae1915acb1a8b4ad10 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 6 Mar 2025 11:43:36 +0000 Subject: [PATCH 54/79] Update module golang.org/x/image to v0.25.0 (forgejo) (#7135) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7135 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 23282f5d9f..0616b43465 100644 --- a/go.mod +++ b/go.mod @@ -102,7 +102,7 @@ require ( gitlab.com/gitlab-org/api/client-go v0.123.0 go.uber.org/mock v0.4.0 golang.org/x/crypto v0.36.0 - golang.org/x/image v0.23.0 + golang.org/x/image v0.25.0 golang.org/x/net v0.36.0 golang.org/x/oauth2 v0.27.0 golang.org/x/sync v0.12.0 diff --git a/go.sum b/go.sum index 70c0161c07..9b80887a74 100644 --- a/go.sum +++ b/go.sum @@ -1539,8 +1539,8 @@ golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeap golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= -golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= From fc4458bfbbc6b7c18942d87d18a0c3b148112117 Mon Sep 17 00:00:00 2001 From: christopher-besch Date: Thu, 6 Mar 2025 12:49:24 +0000 Subject: [PATCH 55/79] interpret Precedence: auto_reply as an auto reply (#7137) Some email clients like to be special and only set the "Precedence" header to "auto_reply" when sending automatic replies. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7137 Reviewed-by: Gusted Reviewed-by: Otto Co-authored-by: christopher-besch Co-committed-by: christopher-besch --- services/mailer/incoming/incoming.go | 4 ++++ services/mailer/incoming/incoming_test.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go index 249dac66cd..092f738cd9 100644 --- a/services/mailer/incoming/incoming.go +++ b/services/mailer/incoming/incoming.go @@ -297,6 +297,10 @@ func isAutomaticReply(env *enmime.Envelope) bool { if autoReply == "yes" { return true } + precedence := env.GetHeader("Precedence") + if precedence == "auto_reply" { + return true + } autoRespond := env.GetHeader("X-Autorespond") return autoRespond != "" } diff --git a/services/mailer/incoming/incoming_test.go b/services/mailer/incoming/incoming_test.go index 6101bc7e32..fe11c9e5c6 100644 --- a/services/mailer/incoming/incoming_test.go +++ b/services/mailer/incoming/incoming_test.go @@ -65,6 +65,12 @@ func TestIsAutomaticReply(t *testing.T) { }, Expected: true, }, + { + Headers: map[string]string{ + "Precedence": "auto_reply", + }, + Expected: true, + }, } for _, c := range cases { From a2958f5a26a0acf0ee5b0139a713df8f1dd5debe Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 6 Mar 2025 23:26:08 +0000 Subject: [PATCH 56/79] fix: consider public issues for project boards (#7143) - The security patch of forgejo/forgejo#6843 fixed the issue where project boards loaded all issues without considering if the doer actually had permission to view that issue. Within that patch the call to `Issues` was modified to include this permission checking. - The query being generated was not entirely correct. Issues in public repositories weren't considered correctly (partly the fault of not setting `AllPublic` unconditionally) in the cause an authenticated user loaded the project. - This is now fixed by setting `AllPublic` unconditionally and subsequently fixing the `Issue` function to ensure that the combination of setting `AllPublic` and `User` generates the correct query, by combining the permission check and issues in public repositories as one `AND` query. - Added unit testing. - Added integration testing. - Resolves Codeberg/Community#1809 - Regression of https://codeberg.org/forgejo/forgejo/pulls/6843 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7143 Reviewed-by: Otto Co-authored-by: Gusted Co-committed-by: Gusted --- .../fixtures/PrivateIssueProjects/project.yml | 4 +- .../PrivateIssueProjects/project_issue.yml | 12 ++++++ models/issues/issue_project.go | 3 +- models/issues/issue_project_test.go | 39 ++++++++++++++----- models/issues/issue_search.go | 22 ++++++++--- tests/integration/private_project_test.go | 23 +++++++---- 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/models/fixtures/PrivateIssueProjects/project.yml b/models/fixtures/PrivateIssueProjects/project.yml index cf63e4e413..8950b33606 100644 --- a/models/fixtures/PrivateIssueProjects/project.yml +++ b/models/fixtures/PrivateIssueProjects/project.yml @@ -1,6 +1,6 @@ - id: 1001 - title: Org project that contains private issues + title: Org project that contains private and public issues owner_id: 3 repo_id: 0 is_closed: false @@ -12,7 +12,7 @@ - id: 1002 - title: User project that contains private issues + title: User project that contains private and public issues owner_id: 2 repo_id: 0 is_closed: false diff --git a/models/fixtures/PrivateIssueProjects/project_issue.yml b/models/fixtures/PrivateIssueProjects/project_issue.yml index 222b2e5f71..0245fb47f3 100644 --- a/models/fixtures/PrivateIssueProjects/project_issue.yml +++ b/models/fixtures/PrivateIssueProjects/project_issue.yml @@ -9,3 +9,15 @@ issue_id: 7 project_id: 1002 project_board_id: 1002 + +- + id: 1003 + issue_id: 16 + project_id: 1001 + project_board_id: 1001 + +- + id: 1004 + issue_id: 1 + project_id: 1002 + project_board_id: 1002 diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index f606b713cf..697ef7fad6 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -56,12 +56,11 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, doer *us ProjectID: b.ProjectID, SortType: "project-column-sorting", IsClosed: isClosed, + AllPublic: true, } if doer != nil { issueOpts.User = doer issueOpts.Org = org - } else { - issueOpts.AllPublic = true } issueList, err := Issues(ctx, issueOpts) diff --git a/models/issues/issue_project_test.go b/models/issues/issue_project_test.go index 6ebc803722..e28b52128e 100644 --- a/models/issues/issue_project_test.go +++ b/models/issues/issue_project_test.go @@ -33,12 +33,13 @@ func TestPrivateIssueProjects(t *testing.T) { defer tests.PrintCurrentTest(t)() issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, user2, org, optional.None[bool]()) require.NoError(t, err) - assert.Len(t, issueList, 1) - assert.EqualValues(t, 6, issueList[0].ID) + assert.Len(t, issueList, 2) + assert.EqualValues(t, 16, issueList[0].ID) + assert.EqualValues(t, 6, issueList[1].ID) issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.None[bool]()) require.NoError(t, err) - assert.EqualValues(t, 1, issuesNum) + assert.EqualValues(t, 2, issuesNum) issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.Some(true)) require.NoError(t, err) @@ -46,18 +47,27 @@ func TestPrivateIssueProjects(t *testing.T) { issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, user2, org, optional.Some(false)) require.NoError(t, err) - assert.EqualValues(t, 1, issuesNum) + assert.EqualValues(t, 2, issuesNum) }) t.Run("Anonymous user", func(t *testing.T) { defer tests.PrintCurrentTest(t)() issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, nil, org, optional.None[bool]()) require.NoError(t, err) - assert.Empty(t, issueList) + assert.Len(t, issueList, 1) + assert.EqualValues(t, 16, issueList[0].ID) issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, orgProject, nil, org, optional.None[bool]()) require.NoError(t, err) + assert.EqualValues(t, 1, issuesNum) + + issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, nil, org, optional.Some(true)) + require.NoError(t, err) assert.EqualValues(t, 0, issuesNum) + + issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, orgProject, nil, org, optional.Some(false)) + require.NoError(t, err) + assert.EqualValues(t, 1, issuesNum) }) }) @@ -69,12 +79,13 @@ func TestPrivateIssueProjects(t *testing.T) { defer tests.PrintCurrentTest(t)() issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, user2, nil, optional.None[bool]()) require.NoError(t, err) - assert.Len(t, issueList, 1) + assert.Len(t, issueList, 2) assert.EqualValues(t, 7, issueList[0].ID) + assert.EqualValues(t, 1, issueList[1].ID) issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, userProject, user2, nil, optional.None[bool]()) require.NoError(t, err) - assert.EqualValues(t, 1, issuesNum) + assert.EqualValues(t, 2, issuesNum) issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, userProject, user2, nil, optional.Some(true)) require.NoError(t, err) @@ -82,19 +93,27 @@ func TestPrivateIssueProjects(t *testing.T) { issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, userProject, user2, nil, optional.Some(false)) require.NoError(t, err) - assert.EqualValues(t, 1, issuesNum) + assert.EqualValues(t, 2, issuesNum) }) t.Run("Anonymous user", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - issueList, err := issues.LoadIssuesFromColumn(db.DefaultContext, column, nil, nil, optional.None[bool]()) require.NoError(t, err) - assert.Empty(t, issueList) + assert.Len(t, issueList, 1) + assert.EqualValues(t, 1, issueList[0].ID) issuesNum, err := issues.NumIssuesInProject(db.DefaultContext, userProject, nil, nil, optional.None[bool]()) require.NoError(t, err) + assert.EqualValues(t, 1, issuesNum) + + issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, userProject, nil, nil, optional.Some(true)) + require.NoError(t, err) assert.EqualValues(t, 0, issuesNum) + + issuesNum, err = issues.NumIssuesInProject(db.DefaultContext, userProject, nil, nil, optional.Some(false)) + require.NoError(t, err) + assert.EqualValues(t, 1, issuesNum) }) }) } diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index e9f116bfc6..56c219af39 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -49,9 +49,13 @@ type IssuesOptions struct { //nolint // prioritize issues from this repo PriorityRepoID int64 IsArchived optional.Option[bool] - Org *organization.Organization // issues permission scope - Team *organization.Team // issues permission scope - User *user_model.User // issues permission scope + + // If combined with AllPublic, then private as well as public issues + // that matches the criteria will be returned, if AllPublic is false + // only the private issues will be returned. + Org *organization.Organization // issues permission scope + Team *organization.Team // issues permission scope + User *user_model.User // issues permission scope } // applySorts sort an issues-related session based on the provided @@ -196,7 +200,8 @@ func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) { } else if len(opts.RepoIDs) > 1 { opts.RepoCond = builder.In("issue.repo_id", opts.RepoIDs) } - if opts.AllPublic { + // If permission scoping is set, then we set this condition at a later stage. + if opts.AllPublic && opts.User == nil { if opts.RepoCond == nil { opts.RepoCond = builder.NewCond() } @@ -268,7 +273,14 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) { applyLabelsCondition(sess, opts) if opts.User != nil { - sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value())) + cond := issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()) + // If AllPublic was set, then also consider all issues in public + // repositories in addition to the private repositories the user has access + // to. + if opts.AllPublic { + cond = cond.Or(builder.In("issue.repo_id", builder.Select("id").From("repository").Where(builder.Eq{"is_private": false}))) + } + sess.And(cond) } } diff --git a/tests/integration/private_project_test.go b/tests/integration/private_project_test.go index 1a4adb4366..663c1c1e75 100644 --- a/tests/integration/private_project_test.go +++ b/tests/integration/private_project_test.go @@ -24,7 +24,7 @@ func TestPrivateIssueProject(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) sess := loginUser(t, user2.Name) - test := func(t *testing.T, sess *TestSession, username string, projectID int64, hasAccess bool) { + test := func(t *testing.T, sess *TestSession, username string, projectID int64, hasAccess bool, publicIssueHref ...string) { t.Helper() defer tests.PrintCurrentTest(t, 1)() @@ -35,9 +35,9 @@ func TestPrivateIssueProject(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) openCloseStats := htmlDoc.Find(".milestone-toolbar .group").First().Text() if hasAccess { - assert.Contains(t, openCloseStats, "1\u00a0Open") + assert.Contains(t, openCloseStats, "2\u00a0Open") } else { - assert.Contains(t, openCloseStats, "0\u00a0Open") + assert.Contains(t, openCloseStats, "1\u00a0Open") } assert.Contains(t, openCloseStats, "0\u00a0Closed") @@ -46,14 +46,21 @@ func TestPrivateIssueProject(t *testing.T) { resp = sess.MakeRequest(t, req, http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) - htmlDoc.AssertElement(t, ".project-column .issue-card", hasAccess) + issueCardsLen := htmlDoc.Find(".project-column .issue-card").Length() + if hasAccess { + assert.EqualValues(t, 2, issueCardsLen) + } else { + assert.EqualValues(t, 1, issueCardsLen) + // Ensure that the public issue is shown. + assert.EqualValues(t, publicIssueHref[0], htmlDoc.Find(".project-column .issue-card .issue-card-title").AttrOr("href", "")) + } // And that the issue count is correct. issueCount := strings.TrimSpace(htmlDoc.Find(".project-column-issue-count").Text()) if hasAccess { - assert.EqualValues(t, "1", issueCount) + assert.EqualValues(t, "2", issueCount) } else { - assert.EqualValues(t, "0", issueCount) + assert.EqualValues(t, "1", issueCount) } } @@ -66,7 +73,7 @@ func TestPrivateIssueProject(t *testing.T) { }) t.Run("Anonymous user", func(t *testing.T) { - test(t, emptyTestSession(t), org.Name, orgProject.ID, false) + test(t, emptyTestSession(t), org.Name, orgProject.ID, false, "/org3/repo21/issues/1") }) }) @@ -78,7 +85,7 @@ func TestPrivateIssueProject(t *testing.T) { }) t.Run("Anonymous user", func(t *testing.T) { - test(t, emptyTestSession(t), user2.Name, userProject.ID, false) + test(t, emptyTestSession(t), user2.Name, userProject.ID, false, "/user2/repo1/issues/1") }) }) } From 17106721f098c41c153895ed64abf937d1accbe3 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 7 Mar 2025 00:45:40 +0000 Subject: [PATCH 57/79] Update module github.com/caddyserver/certmagic to v0.22.0 (forgejo) (#7146) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7146 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 0616b43465..54d2136b90 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blevesearch/bleve/v2 v2.4.4 github.com/buildkite/terminal-to-html/v3 v3.16.6 - github.com/caddyserver/certmagic v0.21.7 + github.com/caddyserver/certmagic v0.22.0 github.com/chi-middleware/proxy v1.1.1 github.com/djherbis/buffer v1.2.0 github.com/djherbis/nio/v3 v3.0.1 @@ -68,7 +68,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/klauspost/compress v1.17.11 - github.com/klauspost/cpuid/v2 v2.2.9 + github.com/klauspost/cpuid/v2 v2.2.10 github.com/lib/pq v1.10.9 github.com/markbates/goth v1.80.0 github.com/mattn/go-isatty v0.0.20 @@ -103,7 +103,7 @@ require ( go.uber.org/mock v0.4.0 golang.org/x/crypto v0.36.0 golang.org/x/image v0.25.0 - golang.org/x/net v0.36.0 + golang.org/x/net v0.37.0 golang.org/x/oauth2 v0.27.0 golang.org/x/sync v0.12.0 golang.org/x/sys v0.31.0 @@ -216,13 +216,13 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/libdns/libdns v0.2.2 // indirect + github.com/libdns/libdns v0.2.3 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/markbates/going v1.0.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mholt/acmez/v3 v3.0.1 // indirect - github.com/miekg/dns v1.1.62 // indirect + github.com/mholt/acmez/v3 v3.1.0 // indirect + github.com/miekg/dns v1.1.63 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -268,9 +268,9 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.24.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.28.0 // indirect + golang.org/x/tools v0.31.0 // indirect google.golang.org/api v0.203.0 // indirect google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect diff --git a/go.sum b/go.sum index 9b80887a74..642469762f 100644 --- a/go.sum +++ b/go.sum @@ -769,8 +769,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buildkite/terminal-to-html/v3 v3.16.6 h1:QKHWPjAnKQnV1hVG/Nb2TwYDr4pliSbvCQDENk8EaJo= github.com/buildkite/terminal-to-html/v3 v3.16.6/go.mod h1:PgzeBymbRFC8I2m46Sci3S18AbwonEgpaz3TGhD7EPs= -github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg= -github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI= +github.com/caddyserver/certmagic v0.22.0 h1:hi2skv2jouUw9uQUEyYSTTmqPZPHgf61dOANSIVCLOw= +github.com/caddyserver/certmagic v0.22.0/go.mod h1:Vc0msarAPhOagbDc/SU6M2zbzdwVuZ0lkTh2EqtH4vs= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -1205,8 +1205,8 @@ github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -1225,8 +1225,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= -github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8= +github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI= github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -1253,12 +1253,12 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY= github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g= -github.com/mholt/acmez/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8= -github.com/mholt/acmez/v3 v3.0.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/mholt/acmez/v3 v3.1.0 h1:RlOx2SSZ8dIAM5GfkMe8TdaxjjkiHTGorlMUt8GeMzg= +github.com/mholt/acmez/v3 v3.1.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= @@ -1573,8 +1573,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1640,8 +1640,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1898,8 +1898,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From ca0f2a0adf8524d043328678ff740d0e7f24396f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 7 Mar 2025 10:27:10 +0000 Subject: [PATCH 58/79] Update module golang.org/x/tools/cmd/deadcode to v0.31.0 (forgejo) (#7150) Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 86b1771d01..4f88b2b0a5 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renova XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go -DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.30.0 # renovate: datasource=go +DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.31.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go RENOVATE_NPM_PACKAGE ?= renovate@39.185.0 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate From 179f9b37f538c925d508916522f82d6bb7ae5179 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 7 Mar 2025 10:27:51 +0000 Subject: [PATCH 59/79] Update module golang.org/x/oauth2 to v0.28.0 (forgejo) (#7149) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7149 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 54d2136b90..5a97bfbce2 100644 --- a/go.mod +++ b/go.mod @@ -104,7 +104,7 @@ require ( golang.org/x/crypto v0.36.0 golang.org/x/image v0.25.0 golang.org/x/net v0.37.0 - golang.org/x/oauth2 v0.27.0 + golang.org/x/oauth2 v0.28.0 golang.org/x/sync v0.12.0 golang.org/x/sys v0.31.0 golang.org/x/text v0.23.0 diff --git a/go.sum b/go.sum index 642469762f..6405ccd3e8 100644 --- a/go.sum +++ b/go.sum @@ -1671,8 +1671,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From b2d01fdde6dc71103c97d206e97c0d80dd290cee Mon Sep 17 00:00:00 2001 From: Codeberg Translate Date: Fri, 7 Mar 2025 14:26:37 +0000 Subject: [PATCH 60/79] i18n: update of translations from Codeberg Translate (#7066) Co-authored-by: lordwektabyte Co-authored-by: Marti Co-authored-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: alanmena Co-authored-by: Juno Takano Co-authored-by: Benedikt Straub Co-authored-by: Edgarsons Co-authored-by: Wuzzy Co-authored-by: justbispo Co-authored-by: Kita Ikuyo Co-authored-by: Fjuro Co-authored-by: Gusted Co-authored-by: kwoot Co-authored-by: SomeTr Co-authored-by: monty24 Co-authored-by: Sampo Harjula Co-authored-by: tacaly Co-authored-by: artnay Co-authored-by: Outbreak2096 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7066 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Codeberg Translate Co-committed-by: Codeberg Translate --- options/locale/locale_ca.ini | 19 ++- options/locale/locale_cs-CZ.ini | 8 +- options/locale/locale_da.ini | 81 ++++++++++- options/locale/locale_de-DE.ini | 34 ++++- options/locale/locale_es-ES.ini | 8 +- options/locale/locale_fa-IR.ini | 27 ++-- options/locale/locale_fi-FI.ini | 30 ++++- options/locale/locale_fil.ini | 138 +++++++++++-------- options/locale/locale_ga-IE.ini | 4 +- options/locale/locale_gl.ini | 28 +++- options/locale/locale_lv-LV.ini | 186 +++++++++++++++----------- options/locale/locale_nds.ini | 12 +- options/locale/locale_nl-NL.ini | 76 +++++++---- options/locale/locale_pt-BR.ini | 56 ++++++-- options/locale/locale_pt-PT.ini | 48 +++++-- options/locale/locale_ru-RU.ini | 34 ++++- options/locale/locale_sv-SE.ini | 2 +- options/locale/locale_uk-UA.ini | 64 ++++++--- options/locale/locale_zh-CN.ini | 10 +- options/locale_next/locale_cs-CZ.json | 26 ++-- options/locale_next/locale_da.json | 16 ++- options/locale_next/locale_de-DE.json | 23 ++-- options/locale_next/locale_fa-IR.json | 20 ++- options/locale_next/locale_lv-LV.json | 26 ++-- options/locale_next/locale_nds.json | 24 ++-- options/locale_next/locale_nl-NL.json | 24 ++-- options/locale_next/locale_pt-BR.json | 26 ++-- options/locale_next/locale_pt-PT.json | 25 ++-- options/locale_next/locale_ru-RU.json | 26 ++-- options/locale_next/locale_uk-UA.json | 26 ++-- options/locale_next/locale_zh-CN.json | 16 ++- 31 files changed, 809 insertions(+), 334 deletions(-) diff --git a/options/locale/locale_ca.ini b/options/locale/locale_ca.ini index 4231a68e8a..9cb7d5e50c 100644 --- a/options/locale/locale_ca.ini +++ b/options/locale/locale_ca.ini @@ -115,7 +115,7 @@ write = Escriure preview = Previsualitzar loading = Carregant… error = Error -error404 = La pàgina a la que estàs intentant arribar no existeix o no estàs autoritzat a veure-la. +error404 = La pàgina a la qual estàs intentant arribar no existeix, ha sigut eliminada o no estàs autoritzat a veure-la. go_back = Tornar Enrere invalid_data = Dades invalides: %v unknown = Desconegut @@ -173,7 +173,7 @@ pull_kind = Cerca "pulls"... exact = Exacte exact_tooltip = Inclou només resultats que són exactament el terme de cerca issue_kind = Cerca problemes... -regexp = Expressió regular +regexp = RegExp regexp_tooltip = Interpreta el terme de cerca com una expressió regular [heatmap] @@ -213,7 +213,7 @@ reinstall_error = Estas intentant instaŀlar sobre una base de dades existent de reinstall_confirm_message = Reinstaŀlar amb una base de dades existent de Forgejo pot causar diferents problemes. En la majoria de casos, s'hauria d'utilitzar l'"app.ini" existent per executar Forgejo. Si saps el que estàs fent, confirma el seguent: no_admin_and_disable_registration = No pot deshabilitar l'autoregistre d'usuaris sense crear un compte d'administrador. err_admin_name_is_reserved = El nom d'usuari "Administrador" no es vàlid: està reservat -smtp_addr = Hoste SMPT +smtp_addr = Hoste SMTP smtp_port = Port SMPT smtp_from = Enviar correu com a mailer_user = Nom d'usuari SMTP @@ -410,6 +410,9 @@ oauth_signin_tab = Vincular a un compte existent oauth.signin.error = Hi ha hagut un error processant la sol·licitud d'autorització. Si persisteix, poseu-vos en contacte amb l'administrador del lloc. disable_forgot_password_mail_admin = La recuperació de comptes només està disponible quan s'ha configurat el correu electrònic. Si us plau, configureu el correu electrònic per a habilitar la recuperació de comptes. non_local_account = Els usuaris no locals no poden actualitzar la seva contrasenya mitjançant l'interfície web de Forgejo +openid_register_desc = No s'ha reconegut la URI OpenID. Vinculeu-la amb un compte nou aquí. +openid_connect_desc = No s'ha reconegut la URI OpenID. Vinculeu-la amb un compte nou aquí. +sign_in_openid = Accediu amb OpenID [editor] buttons.indent.tooltip = Aniua els elements un nivell @@ -434,6 +437,10 @@ table_modal.placeholder.header = Capçalera table_modal.placeholder.content = Contingut table_modal.label.rows = Files table_modal.label.columns = Columnes +link_modal.header = Afegeix un enllaç +link_modal.url = URL +link_modal.description = Descripció +link_modal.paste_reminder = Pista: Amb un enllaç en el teu porta-retalls, pots enganxar-la directament a l'editor per a crear un enllaç. [home] my_orgs = Organitzacions @@ -472,4 +479,8 @@ reply = o responeu directament a aquest correu activate_account.text_1 = Hola %[1]s, gràcies per registrar-te a %[2]s! register_notify = Benvinguts a %s admin.new_user.text = Si us plau, cliqueu aui per administrar aquest usuari des del panell d'administració. -admin.new_user.user_info = Informació d'usuari \ No newline at end of file +admin.new_user.user_info = Informació d'usuari +admin.new_user.subject = Nou usuari %s s'acaba d'enregistrar +activate_email.text = Si us plau, cliqueu el següent enllaç per verificar la vostra adreça de correu electrònic en %s +activate_email = Verifica la teva adreça de correu electrònic +activate_account.text_2 = Si us plau, cliqueu l'enllaç següent per activar el vostre compte en %s: \ No newline at end of file diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index fbe5ef49a2..7fc4d1c7aa 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -2721,7 +2721,7 @@ error.csv.unexpected=Tento soubor nelze vykreslit, protože obsahuje neočekáva error.csv.invalid_field_count=Soubor nelze vykreslit, protože má nesprávný počet polí na řádku %d. pulls.made_using_agit = AGit settings.confirm_wiki_branch_rename = Přejmenovat větev wiki -issues.comment.blocked_by_user = U tohoto problému nemůžete vytvořit komentář, protože jste byl/a zablokován/a majitelem repozitáře nebo autorem problému. +issues.comment.blocked_by_user = Tento problém nemůžete okomentovat, protože jste byli zablokováni majitelem repozitáře nebo autorem problému. contributors.contribution_type.additions = Přidání admin.manage_flags = Spravovat vlajky admin.enabled_flags = Vlajky povolené v repozitáři: @@ -2734,7 +2734,7 @@ clone_in_vscodium = Klonovat do VSCodium settings.wiki_rename_branch_main_notices_1 = Tato operace je NEVRATNÁ. settings.wiki_branch_rename_success = Název větve wiki repozitáře byl úspěšně normalizován. rss.must_be_on_branch = Abyste mohli mít zdroj RSS, musíte se nacházet ve větvi. -issues.blocked_by_user = V tomto repozitáři nemůžete vytvořit problém, protože jste byl/a jeho majitelem zablokován/a. +issues.blocked_by_user = V tomto repozitáři nemůžete vytvářet problémy, protože jste byl/a jeho majitelem zablokován/a. migrate.forgejo.description = Migrovat data z codeberg.org nebo jiných instancí Forgejo. mirror_sync = synchronizováno blame.ignore_revs = Ignorování revizí v souboru .git-blame-ignore-revs. Klikněte sem pro udělení výjimky a zobrazení normálního přehledu blame. @@ -2901,6 +2901,10 @@ editor.commit_email = E-mail revize commits.view_single_diff = Zobrazit změny tohoto souboru provedené v této revizi pulls.editable = Upravitelné pulls.editable_explanation = Tato žádost o sloučení umožňuje úpravy správci. Můžete přispět přímo do ní. +issues.reopen.blocked_by_user = Tento problém nemůžete znovu otevřít, protože jste byli zablokováni majitelem repozitáře nebo autorem tohoto problému. +pulls.comment.blocked_by_user = Tuto žádost o sloučení nemůžete okomentovat, protože jste byli zablokováni majitelem repozitáře nebo autorem žádosti. +issues.filter_no_results_placeholder = Zkuste upravit filtry vyhledávání. +issues.filter_no_results = Žádné výsledky [graphs] component_loading_info = Tohle může chvíli trvat… diff --git a/options/locale/locale_da.ini b/options/locale/locale_da.ini index c22c5fffaf..5a79004acb 100644 --- a/options/locale/locale_da.ini +++ b/options/locale/locale_da.ini @@ -213,6 +213,10 @@ table_modal.label.columns = Kolonner buttons.unindent.tooltip = Udsortere genstande med ét niveau buttons.indent.tooltip = Indlejring af genstande med ét niveau buttons.switch_to_legacy.tooltip = Brug den gamle editor i stedet +link_modal.header = Tilføj et link +link_modal.url = Url +link_modal.description = Beskrivelse +link_modal.paste_reminder = Tip: Med en URL i dit udklipsholder kan du indsætte direkte i editoren for at oprette et link. [filter] string.asc = A - Z @@ -978,6 +982,26 @@ change_username_redirect_prompt.with_cooldown.one = Det gamle brugernavn vil væ change_username_redirect_prompt.with_cooldown.few = Det gamle brugernavn vil være tilgængeligt for alle efter en nedkølingsperiode på %[1]d dage, du kan stadig kræve det gamle brugernavn tilbage i nedkølingsperioden. keep_pronouns_private = Vis kun stedord til godkendte brugere keep_pronouns_private.description = Dette vil skjule dine stedord for besøgende, der ikke er logget ind. +quota.applies_to_user = Følgende kvoteregler gælder for din konto +quota.rule.exceeded.helper = Den samlede størrelse af objekter for denne regel har overskredet kvoten. +storage_overview = Opbevaringsoversigt +quota = Kvote +quota.applies_to_org = Følgende kontingentregler gælder for denne organisation +quota.rule.exceeded = Oversteget +quota.rule.no_limit = Ubegrænset +quota.sizes.all = Alle +quota.sizes.repos.all = Depoter +quota.sizes.repos.public = Offentlige depoter +quota.sizes.repos.private = Private depoter +quota.sizes.git.all = Git indhold +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.all = Aktiver +quota.sizes.assets.attachments.all = Vedhæftede filer +quota.sizes.assets.attachments.issues = Problemets vedhæftede filer +quota.sizes.assets.attachments.releases = Udgivelsens vedhæftede filer +quota.sizes.assets.artifacts = Artefakter +quota.sizes.assets.packages.all = Pakker +quota.sizes.wiki = Wiki [repo] rss.must_be_on_branch = Du skal være på en gren for at have et RSS-feed. @@ -1755,8 +1779,8 @@ issues.review.un_resolve_conversation = Uafklaret samtale issues.content_history.delete_from_history = Slet fra historikken issues.content_history.delete_from_history_confirm = Slet fra historikken? issues.content_history.options = Valgmuligheder -issues.blocked_by_user = Du kan ikke oprette et problem på dette depot, fordi du er blokeret af depotes ejer. -issues.comment.blocked_by_user = Du kan ikke oprette en kommentar til dette problem, fordi du er blokeret af depotes ejer eller anmelder af problemet. +issues.blocked_by_user = Du kan ikke oprette problemer på dette depot, fordi du er blokeret af depotes ejer. +issues.comment.blocked_by_user = Du kan ikke kommentere til dette problem, fordi du er blokeret af depotes ejer eller anmelder af problemet. issues.reference_link = Reference: %s compare.compare_base = base compare.compare_head = sammenlign @@ -1856,10 +1880,10 @@ pulls.rebase_merge_pull_request = Rebaser og spole derefter frem pulls.rebase_merge_commit_pull_request = Rebase og opret derefter flet commit pulls.waiting_count_1 = %d venter på gennemgang pulls.invalid_merge_option = Du kan ikke bruge denne fletteindstilling til denne pull-anmodning. -pulls.merge_conflict = Sammenfletning mislykkedes: Der var en konflikt under sammenlægningen. Tip: Prøv en anden strategi +pulls.merge_conflict = Fletning mislykkedes: Der var en konflikt under sammenlægningen. Tip: Prøv en anden strategi pulls.merge_conflict_summary = Fejl besked pulls.rebase_conflict_summary = Fejl besked -pulls.unrelated_histories = Sammenfletning mislykkedes: Sammenfletnings-hovedet og -basen deler ikke en fælles historie. Tip: Prøv en anden strategi +pulls.unrelated_histories = Fletning mislykkedes: Sammenfletnings-hovedet og -basen deler ikke en fælles historie. Tip: Prøv en anden strategi pulls.head_out_of_date = Fletning mislykkedes: Under genereringen af fletningen blev hovedet opdateret. Tip: Prøv igen. pulls.has_merged = Mislykkedes: Pull-anmodningen er blevet flettet, du kan ikke flette igen eller ændre målgrenen. pulls.rebase_conflict = Fletning mislykkedes: Der var en konflikt under re-basering af commit: %[1]s. Tip: Prøv en anden strategi @@ -1867,6 +1891,55 @@ pulls.merge_out_of_date = Fletning mislykkedes: Under genereringen af fletningen pulls.push_rejected_summary = Fuld afvisningsmeddelelse pulls.push_rejected = Push mislykkedes: Pushet blev afvist. Gennemgå Git-hooks for dette depot. pulls.push_rejected_no_message = Push mislykkedes: Pushet blev afvist, men der var ingen fjernmeddelelse. Gennemgå Git-hooks for dette depot +pulls.status_checks_failure = Nogle kontroller mislykkedes +issues.reopen.blocked_by_user = Du kan ikke genåbne dette problem, fordi du er blokeret af depotes ejer eller aflæggeren af dette problem. +pulls.comment.blocked_by_user = Du kan ikke kommentere denne pull-anmodning, fordi du er blokeret af depotes ejer eller plakaten for pull-anmodningen. +pulls.open_unmerged_pull_exists = `Du kan ikke udføre en genåbnings handling, fordi der er en afventende pull-anmodning (#%d) med identiske egenskaber.` +pulls.status_checking = Nogle kontroller afventer +pulls.status_checks_warning = Nogle kontroller rapporterede advarsler +pulls.status_checks_error = Nogle kontroller rapporterede fejl +pulls.cmd_instruction_merge_desc = Flet ændringerne og opdater på Forgejo. +pulls.clear_merge_message = Ryd flettemeddelelse +pulls.cmd_instruction_merge_warning = Advarsel: Indstillingen "Autofind manuel fletning" er ikke aktiveret for dette depot. Du skal efterfølgende markere denne pull-anmodning som manuelt flettet. +pulls.clear_merge_message_hint = Rydning af flettemeddelelsen vil kun fjerne commit-meddelelsens indhold og beholde genererede git-trailere såsom "Co-Authored-By ...". +pulls.reopen_failed.head_branch = Pull-anmodningen kan ikke genåbnes, fordi hovedgrenen ikke eksisterer længere. +pulls.editable_explanation = Denne pull-anmodning tillader redigeringer fra vedligeholdere. Du kan bidrage direkte til det. +pulls.auto_merge_button_when_succeed = (Når kontroller lykkes) +pulls.status_checks_requested = Påkrævet +pulls.close = Luk pull anmodning +pulls.commit_ref_at = `henviste til denne pull-anmodning fra en commit %[2]s` +pulls.cmd_instruction_hint = Se instruktionerne på kommandolinjen +pulls.reopened_at = `genåbnede denne pull-anmodning %[2]s` +pulls.closed_at = `lukkede denne pull-anmodning %[2]s` +pulls.cmd_instruction_checkout_desc = Fra dit projektdepot, tjek en ny gren og test ændringerne. +pulls.editable = Redigerbar +pulls.made_using_agit = AGit +pulls.agit_explanation = Oprettet ved hjælp af AGit-arbejdsgangen. AGit lader bidragydere foreslå ændringer ved hjælp af "git push" uden at oprette en fork eller en ny gren. +pulls.status_checks_success = Alle kontroller lykkedes +pulls.cmd_instruction_merge_title = Flet +pulls.reopen_failed.base_branch = Pull-anmodningen kan ikke genåbnes, fordi basisgrenen ikke eksisterer længere. +pulls.cmd_instruction_checkout_title = Checkout +pulls.outdated_with_base_branch = Denne gren er forældet med basisgrenen +pulls.status_checks_details = Detaljer +pulls.status_checks_hide_all = Skjul alle kontroller +pulls.status_checks_show_all = Vis alle kontroller +pulls.update_branch = Opdater gren ved fletning +pulls.update_branch_rebase = Opdel gren ved genbase +pulls.update_branch_success = Grenopdatering var vellykket +pulls.update_not_allowed = Du har ikke tilladelse til at opdatere grenen +pulls.auto_merge_has_pending_schedule = %[1]s planlagde denne pull-anmodning til automatisk at flette, når alle kontroller lykkes %[2]s. +pulls.auto_merge_cancel_schedule = Annuller automatisk fletning +pulls.auto_merge_not_scheduled = Denne pull-anmodning er ikke planlagt til automatisk fletning. +pulls.auto_merge_canceled_schedule = Den automatiske fletning blev annulleret for denne pull-anmodning. +pulls.auto_merge_newly_scheduled_comment = `planlagde denne pull-anmodning til automatisk at flette, når alle kontroller lykkes %[1]s.` +pulls.auto_merge_canceled_schedule_comment = `annullerede automatisk fletning af denne pull-anmodning, når alle kontroller lykkes %[1]s` +pulls.delete_after_merge.head_branch.is_default = Den hovedgren, du vil slette, er standardgrenen og kan ikke slettes. +pulls.delete_after_merge.head_branch.is_protected = Den hovedgren, du vil slette, er en beskyttet gren og kan ikke slettes. +pulls.delete_after_merge.head_branch.insufficient_branch = Du har ikke tilladelse til at slette hovedgrenen. +pulls.delete.title = Vil du slette denne pull-anmodning? +pulls.delete.text = Vil du virkelig slette denne pull-anmodning? (Dette fjerner alt indhold permanent. Overvej at lukke det i stedet, hvis du har til hensigt at beholde det arkiveret) +pulls.auto_merge_newly_scheduled = Pull-anmodningen var planlagt til at flette, når alle kontroller lykkes. +pulls.auto_merge_when_succeed = Automatisk fletning, når alle kontroller lykkes [notification] watching = Overvåger diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 34a69202b0..ccd50d0718 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -206,6 +206,10 @@ table_modal.placeholder.content = Inhalt table_modal.placeholder.header = Kopfzeile table_modal.label.rows = Zeilen table_modal.label.columns = Spalten +link_modal.header = Einen Link hinzufügen +link_modal.url = URL +link_modal.description = Beschreibung +link_modal.paste_reminder = Hinweis: Wenn du einen URL in der Zwischenablage hast, kannst du durch einfügen im Editor direkt einen Link erstellen. [filter] string.asc=A–Z @@ -1064,6 +1068,26 @@ change_username_redirect_prompt.with_cooldown.one = Der alte Benutzername ist na change_username_redirect_prompt.with_cooldown.few = Der alte Benutzername ist nach einer Schutzzeit von %[1]d Tagen wieder für alle Verfügbar. Du kannst den alten Benutzername während dieser Schutzzeit erneut beanspruchen. keep_pronouns_private = Pronomen nur angemeldeten Nutzern anzeigen keep_pronouns_private.description = Dies verbirgt deine Pronomen von Besuchern die nicht angemeldet sind. +quota.sizes.assets.artifacts = Artefakte +quota.applies_to_user = Die folgenden Quota-Regeln greifen für deinen Account +quota.sizes.assets.attachments.issues = Issue-Anhänge +quota.rule.exceeded.helper = Die Gesamtgröße der Objekte für diese Regel hat die Quota überschritten. +storage_overview = Speicherübersicht +quota = Quota +quota.sizes.assets.attachments.releases = Release-Anhänge +quota.applies_to_org = Die folgenden Quota-Regeln greifen für diese Organisation +quota.rule.exceeded = Überschritten +quota.rule.no_limit = Unbegrenzt +quota.sizes.all = Alle +quota.sizes.repos.all = Repositorys +quota.sizes.repos.public = Öffentliche Repositorys +quota.sizes.repos.private = Private Repositorys +quota.sizes.git.all = Git-Inhalte +quota.sizes.git.lfs = Git-LFS +quota.sizes.assets.all = Assets +quota.sizes.assets.attachments.all = Anhänge +quota.sizes.assets.packages.all = Pakete +quota.sizes.wiki = Wiki [repo] owner=Besitzer @@ -1185,8 +1209,8 @@ template.issue_labels=Issue-Labels template.one_item=Es muss mindestens ein Vorlagenelement ausgewählt werden template.invalid=Es muss ein Vorlagen-Repository ausgewählt werden -archive.title=Dieses Repository ist archiviert. Du kannst Dateien ansehen und es klonen, kannst aber du kannst den Status des Repositorys nicht verändern, zum Beispiel nichts pushen, und keine Issues eröffnen, oder Pull-Requests oder Kommentare erstellen. -archive.title_date=Dieses Repository wurde am %s archiviert. Du kannst Dateien ansehen und es klonen, kannst aber den Status des Repositorys nicht verändern, zum Beispiel nichts pushen, und keine Issues eröffnen, oder Pull-Requests oder Kommentare erstellen. +archive.title=Dieses Repository ist archiviert. Du kannst Dateien ansehen und es klonen, kannst aber du kannst seinen Status nicht verändern, zum Beispiel nichts pushen, und keine Issues eröffnen, oder Pull-Requests oder Kommentare erstellen. +archive.title_date=Dieses Repository wurde am %s archiviert. Du kannst Dateien ansehen und es klonen, kannst aber seinen Status nicht verändern, zum Beispiel nichts pushen, und keine Issues eröffnen, oder Pull-Requests oder Kommentare erstellen. archive.issue.nocomment=Dieses Repository ist archiviert. Du kannst Issues nicht kommentieren. archive.pull.nocomment=Dieses Repository ist archiviert. Du kannst Pull-Requests nicht kommentieren. @@ -2699,7 +2723,7 @@ error.csv.unexpected=Diese Datei kann nicht gerendert werden, da sie ein unerwar error.csv.invalid_field_count=Diese Datei kann nicht gerendert werden, da sie eine falsche Anzahl an Feldern in Zeile %d hat. rss.must_be_on_branch = Du musst auf einem Branch sein, um einen RSS-Feed zu haben. new_repo_helper = Ein Repository enthält alle Projektdateien inklusive der Revisionshistorie. Bereits woanders gehostet? Repository migrieren. -issues.comment.blocked_by_user = Du kannst kein Kommentar für dieses Issue erstellen, weil du vom Repository-Besitzer oder dem Autoren des Issues blockiert wurdest. +issues.comment.blocked_by_user = Du kannst dieses Issue nicht kommentieren, weil du vom Repository-Besitzer oder dem Autoren des Issues blockiert wurdest. clone_in_vscodium = In VSCodium klonen settings.units.add_more = Mehr aktivieren settings.wiki_rename_branch_main_desc = Den Branch, der intern vom Wiki benutzt wird, zu „%s“ umbenennen. Dies ist permanent und kann nicht rückgängig gemacht werden. @@ -2715,7 +2739,7 @@ settings.wiki_branch_rename_success = Der Branch-Name des Repository-Wikis wurde settings.archive.mirrors_unavailable = Spiegel sind nicht verfügbar in archivierten Repos. pulls.blocked_by_user = Du kannst keinen Pull-Request in diesem Repository erstellen, weil du vom Repository-Besitzer blockiert wurdest. settings.add_collaborator_blocked_our = Der Mitarbeiter konnte nicht hinzugefügt werden, weil der Repository-Besitzer ihn blockiert hat. -issues.blocked_by_user = Du kannst kein Issue in diesem Repository erstellen, weil du vom Repository-Besitzer blockiert wurdest. +issues.blocked_by_user = Du kannst keine Issues in diesem Repository erstellen, weil du vom Repository-Besitzer blockiert wurdest. admin.manage_flags = Flags verwalten admin.enabled_flags = Vom Repository aktivierte Flags: admin.update_flags = Flags aktualisieren @@ -2879,6 +2903,8 @@ editor.commit_email = Commit-E-Mail commits.view_single_diff = Änderungen an dieser Datei, die in diesem Commit eingeführt wurden, betrachten pulls.editable = Bearbeitbar pulls.editable_explanation = Dieser Pull-Request erlaubt Bearbeitungen durch Maintainer. Du kannst direkt dazu beitragen. +issues.reopen.blocked_by_user = Du kannst dieses Issue nicht wieder eröffnen, weil du vom Repository-Besitzer oder Ersteller des Issues blockiert wurdest. +pulls.comment.blocked_by_user = Du kannst diesen Pull-Request nicht kommentieren, da du vom Repository-Besitzer oder Ersteller des Pull-Requests blockiert wurdest. [graphs] component_loading_failed = Konnte %s nicht laden diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 4ade0821a1..8b29ef0f4a 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -110,7 +110,7 @@ preview=Vista previa loading=Cargando… error=Error -error404=La página a la que está intentando acceder o no existe o no está autorizado para verla. +error404=La página a la que está intentando acceder no existe,ha sido eliminada o no está autorizado a verla. go_back=Volver never=Nunca @@ -206,6 +206,9 @@ table_modal.placeholder.header = Cabecera table_modal.label.rows = Filas table_modal.label.columns = Columnas table_modal.placeholder.content = Contenido +link_modal.header = Añadir enlace +link_modal.description = Descripción +link_modal.paste_reminder = Pista: Con una URL en tu portapapeles, puedes pegar directamente en el editor para crear un enlace. [filter] string.asc=A - Z @@ -678,6 +681,8 @@ Biography = Biografía Location = Ubicación To = Nombre de rama Website = Sitio web +username_claiming_cooldown = El nombre de usuario no se puede reclamar, debido a que su periodo de protección no ha terminado aún. Puede ser reclamado el %[1]s. +email_domain_is_not_allowed = El dominio de la dirección de correo electrónico del usuario %s entra en conflicto con EMAL_DOMAIN_ALLOWLIST o EMAIL_DOMAIN_BLOCKLIST. Asegúrese de que ha establecido la dirección de correo electrónico correctamente. [user] @@ -1055,6 +1060,7 @@ language.description = Este idioma se guardará en tu cuenta y se utilizará com language.localization_project = ¡Ayúdanos a traducir Forgejo a tu idioma! Más información. pronouns_custom_label = Pronombres personalizados user_block_yourself = No puedes bloquearte a tí mismo. +quota = Cuota [repo] owner=Propietario diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index b6c065f7ee..804b48b2b2 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -209,6 +209,8 @@ buttons.switch_to_legacy.tooltip = به جای آن از ویرایش‌گر ک buttons.code.tooltip = افزودن کد link_modal.header = افزودن پیوند link_modal.url = یوآرال +link_modal.paste_reminder = راهنمایی: با داشتن یک یوآرال در بریده‌‌دان، می‌توانید با رونوشت مستقیم آن در ویرایش‌گرتان یک پیوند بسازید. +link_modal.description = دیباچه [filter] string.asc = آ - ی @@ -235,7 +237,7 @@ install_desc = به آسانی مستندات را مطالعه نمایید. db_title=تنظیمات پایگاه‌داده db_type=گونه پایگاه‌داده @@ -257,24 +259,24 @@ err_empty_db_path=مسیر دیتابیس SQLite3 نمیتواند خالی با no_admin_and_disable_registration=شما بدون ایجاد حساب‌ کاربری مدیر نمی‌توانید عضویت را غیر فعال کنید. err_empty_admin_password=کلمه عبور حساب مدیر نمی تواند خالی باشد. err_empty_admin_email=رایانامه (ایمیل) مدیر نمی تواند خالی باشد. -err_admin_name_is_reserved=نام کاربری مدیر اشتباه است. نام کاربری قبلا استفاده شده است +err_admin_name_is_reserved=نام‌کاربری مدیر اشتباه است، نام‌کاربری قبلا استفاده شده است err_admin_name_pattern_not_allowed=نام کاربری مدیر اشتباه است. نام کاربری قبلا استفاده شده است err_admin_name_is_invalid=نام کابری مدیر اشتباه است general_title=تنظیمات عمومی -app_name=عنوان سایت -app_name_helper=شما می توانید نام شرکت خود را در اینجا وارد کنید. +app_name=تیتر نمونه +app_name_helper=شما می توانید نام نمونه خود را در اینجا وارد کنید. آن در همه برگه‌ها به نمایش در خواهد آمد. repo_path=مسیر ریشه مخزن repo_path_helper=تمام مخازن کد راه دور در این پوشه ذخیره می‌شوند. -lfs_path=مسیر Git LFS +lfs_path=مسیر ریشه ذخیره‌سازی پرونده بزرگ گیت lfs_path_helper=فایل هایی که توسط Git LFS دنبال میشوند در این پوشه ذخیره خواهند شد. درصورت خالی بودن فیلد این قابلیت غیرفعال خواهد بود. -run_user=اجرا به عنوان نام کاربری -domain=دامنه سرور +run_user=اجرای کاربر به عنوان +domain=دامنه کارساز domain_helper=آدرس میزبان یا دامنه برای سرور. -ssh_port=پورت SSH سرور -ssh_port_helper=شماره درگاهی که سرور SSH گوش می دهد. برای غیر فعال کردن خالی بگذارید. -http_port=پورت HTTP گیتی -http_port_helper=پورت سرور وب گیتی. +ssh_port=درگاه کارساز پوسته‌امن +ssh_port_helper=شماره درگاهی که سرور پوسته‌امن استفاده می‌کند. برای غیر فعال کردن خالی بگذارید. +http_port=درگاه شنونده اچ‌تی‌تی‌پی +http_port_helper=شماره درگاهی که توسط کارساز وب فرججو استفاده می‌شود. app_url=آدرس پایه گیتی app_url_helper=آدرس پایه برای URLهای اجماع HTTP(S) و هشدار های رایانامه (ایمیل). log_root_path=مسیر گزارش‌ها @@ -335,6 +337,9 @@ no_reply_address=مخفی کردن دامنه ایمیل no_reply_address_helper=نام دامنه برای کاربران دارای آدرس ایمیل پنهان است. به عنوان مثال ، اگر نام دامنه ایمیل مخفی روی "noreply.example.org" تنظیم شده باشد ، نام کاربری "joe" در Git به عنوان "joe@noreply.example.org" وارد می شود password_algorithm=الگوریتم درهم‌ساز گذرواژه require_db_desc = فروججو به مای‌اس‌کیوال، پستگری‌اس‌کیوال،اس‌کیولایت۳ یا تی‌دی‌بی نیاز دارد. +run_user_helper = نام‌کاربری سامانه‌عامل که با عنوان فرججو اجرا می‌شود. به یادداشته باشید که کاربر باید دسترسی به مسیر ریشه مخازن داشته باشد. +app_slogan_helper = شعار نمونه خود را اینجا وارد کنید. برای غیرفعال شدن خالی بگذارید. +app_slogan = شعار نمونه [home] uname_holder=نام کاربری یا نشانی ایمیل diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 9d5ac3c43e..9c011b7fd1 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -98,7 +98,7 @@ preview=Esikatselu loading=Ladataan… error=Virhe -error404=Sivu, jota yrität nähdä, joko ei löydy tai et ole oikeutettu katsomaan sitä. +error404=Sivu, jota yrität nähdä, joko ei löydy, on poistettu tai et ole oikeutettu katsomaan sitä. never=Ei koskaan @@ -206,6 +206,10 @@ table_modal.placeholder.content = Sisältö table_modal.label.rows = Rivit table_modal.label.columns = Sarakkeet buttons.unindent.tooltip = Vähennä sisennystä yhden tason verran +link_modal.header = Lisää linkki +link_modal.url = Osoite +link_modal.description = Kuvaus +link_modal.paste_reminder = Vihje: Jos leikepöydällä on URL-osoite, voit liittää suoraan editoriin luodaksesi linkin. [filter] string.asc = A - Ö @@ -620,6 +624,8 @@ unable_verify_ssh_key = SSH-avainta ei voi vahvistaa, tarkista se mahdollisten v url_error = `"%s" ei ole kelvollinen URL-osoite.` must_use_public_key = Antamasi avain on yksityinen avain. Älä lähetä yksityistä avaintasi mihinkään. Käytä sen sijaan julkista avaintasi. still_own_packages = Tilisi omistaa yhden tai useamman paketin, poista ne ensin. +AccessToken = Pääsypoletti +enterred_invalid_owner_name = Uuden omistajan nimi ei ole kelvollinen. [user] @@ -947,6 +953,21 @@ openid_deletion_desc = Tämän OpenID-osoitteen poistaminen tililtäsi estää k generate_token_name_duplicate = Nimeä %s on jo käytetty sovelluksen nimenä. Käytä eri nimeä. ssh_signonly = SSH on tällä hetkellä poistettu käytöstä, joten näitä avaimia käytetään vain kommittien allekirjoituksen vahvistamiseen. oauth2_applications_desc = OAuth2-sovellukset mahdollistavat käyttämäsi kolmannen osapuolen sovelluksen todentaa turvallisesti käyttäjiä tähän Forgejo-instanssiin. +quota.sizes.assets.attachments.all = Liitteet +quota.applies_to_user = Seuraavia kiintiösääntöjä sovelletaan tiliisi +user_block_yourself = Et voi estää itseäsi. +quota = Kiintiö +storage_overview = Tallennustilan yleisnäkymä +quota.applies_to_org = Seuraavia kiintiösääntöjä sovelletaan tähän organisaatioon +quota.rule.exceeded = Ylitetty +quota.rule.no_limit = Rajoittamaton +quota.sizes.all = Kaikki +quota.sizes.repos.all = Repot +quota.sizes.repos.public = Julkiset repot +quota.sizes.repos.private = Yksityiset repot +quota.sizes.git.all = Git-sisältö +quota.sizes.assets.packages.all = Paketit +quota.sizes.wiki = Wiki [repo] owner=Omistaja @@ -2096,6 +2117,13 @@ settings.delete_notices_2 = - Tämä toiminto poistaa pysyvästi repon % issues.filter_assginee_no_select = Kaikki käsittelijät issues.new.assign_to_me = Osoita itselle pulls.closed_at = `sulki tämän vetopyynnön %[2]s` +tree_path_not_found_branch = Polkua %[1]s ei ole olemassa haarassa %[2]s +transfer.no_permission_to_reject = Sinulla ei ole oikeutta hylätä tätä siirtoa. +generate_repo = Luo repo +tree_path_not_found_commit = Polkua %[1]s ei ole olemassa kommitissa %[2]s +archive.pull.noreview = Tämä repo on arkistoitu. Et voi katselmoida vetopyyntöjä. +tree_path_not_found_tag = Polkua %[1]s ei ole olemassa tagissa %[2]s +transfer.no_permission_to_accept = Sinulla ei ole oikeutta hyväksyä tätä siirtoa. diff --git a/options/locale/locale_fil.ini b/options/locale/locale_fil.ini index 31e0e2135b..12b43f93e9 100644 --- a/options/locale/locale_fil.ini +++ b/options/locale/locale_fil.ini @@ -231,10 +231,10 @@ ssl_mode = SSL path = Daanan sqlite_helper = File path para sa SQLite3 database.
Maglagay ng absolute path kapag tinatakbo mo ang Forgejo bilang serbisyo. reinstall_confirm_check_3 = Kinukumprima mo na sigurado ka talaga na ang Forgejo na ito ay tumatakbo sa tamang app.ini na lokasyon at sigurado ka na kailangan mo mag-reinstall. Kinukumpirma mo na kilalanin ang mga panganib sa itaas. -err_empty_db_path = Hindi maaring walang laman ang path ng SQLite database. -no_admin_and_disable_registration = Hindi mo maaring i-disable ang user self-registration nang hindi gumawa ng isang tagapangasiwa na account. -err_empty_admin_password = Hindi maaring walang laman ang password ng tagapangasiwa. -err_empty_admin_email = Hindi maaring walang laman ang email ng tagapangasiwa. +err_empty_db_path = Hindi maaaring walang laman ang path ng SQLite database. +no_admin_and_disable_registration = Hindi mo maaaring i-disable ang user self-registration nang hindi gumawa ng isang tagapangasiwa na account. +err_empty_admin_password = Hindi maaaring walang laman ang password ng tagapangasiwa. +err_empty_admin_email = Hindi maaaring walang laman ang email ng tagapangasiwa. err_admin_name_is_reserved = Hindi angkop ang username ng tagapangasiwa, naka-reserba ang username err_admin_name_is_invalid = Hindi angkop ang username ng tagapangasiwa general_title = Mga General Setting @@ -250,9 +250,9 @@ domain_helper = Domain o host para sa server na ito. ssh_port = Port ng SSH Server http_port = Listen port sa HTTP lfs_path_helper = Ang mga file na naka-track sa Git LFS ay ilalagay sa directory na ito. Iwanang walang laman para i-disable. -reinstall_confirm_message = Ang pag-install muli na may umiiral na Forgejo database ay maaring magdulot ng mga problema. Sa karamihan ng mga kaso, dapat mong gamitin ang iyong umiiral na "app.ini" para patakbuhin ang Forgejo. Kung alam mo ang ginagawa mo, kumpirmahin ang mga sumusunod: -reinstall_confirm_check_1 = Ang data na naka-encrypt sa pamamagitan ng SECRET_KEY sa app.ini ay maaring mawala: baka hindi maka-log in ang mga user gamit ng 2FA/OTP at ang mga mirror ay maaring hindi gumana mg maayos. Sa pamamagitan ng pag-check ng box na ito kinukumpirma mo na ang kasalukuyang app.ini file ay naglalaman ng tamang SECRET_KEY. -reinstall_confirm_check_2 = Ang mga repositoryo at mga setting ay maaring kailangang i-resynchronize. Sa pamamagitan ng pag-check ng box na ito kinukumprima mo na ire-resynchronize mo ang mga hook para sa mga repositoryo at authorized_keys ng mano-mano. Kinukumpirma mo na sisiguraduhin mo na tama ang mga setting ng repositoryo at mirror. +reinstall_confirm_message = Ang pag-install muli na may umiiral na Forgejo database ay maaaring magdulot ng mga problema. Sa karamihan ng mga kaso, dapat mong gamitin ang iyong umiiral na "app.ini" para patakbuhin ang Forgejo. Kung alam mo ang ginagawa mo, kumpirmahin ang mga sumusunod: +reinstall_confirm_check_1 = Ang data na naka-encrypt sa pamamagitan ng SECRET_KEY sa app.ini ay maaaring mawala: baka hindi maka-log in ang mga user gamit ng 2FA/OTP at ang mga mirror ay maaaring hindi gumana mg maayos. Sa pamamagitan ng pag-check ng box na ito kinukumpirma mo na ang kasalukuyang app.ini file ay naglalaman ng tamang SECRET_KEY. +reinstall_confirm_check_2 = Ang mga repositoryo at mga setting ay maaaring kailangang i-resynchronize. Sa pamamagitan ng pag-check ng box na ito kinukumprima mo na ire-resynchronize mo ang mga hook para sa mga repositoryo at authorized_keys ng mano-mano. Kinukumpirma mo na sisiguraduhin mo na tama ang mga setting ng repositoryo at mirror. err_admin_name_pattern_not_allowed = Hindi angkop ang username ng tagapangasiwa, ang username ay tumutugma sa reserved pattern ssh_port_helper = Numero ng port na gagamitin ng SSH server. Iwanang walang laman para i-disable ang SSH server. server_service_title = Mga setting ng server at third-party na serbisyo @@ -362,6 +362,10 @@ table_modal.placeholder.content = Nilalaman table_modal.header = Magdagdag ng table table_modal.label.rows = Mga Row table_modal.label.columns = Mga Column +link_modal.header = Magdagdag ng link +link_modal.url = Url +link_modal.description = Deskripsyon +link_modal.paste_reminder = Pahiwatig: Kapag may URL sa clipboard, maari mong direktang i-paste sa editor para gumawa ng link. [filter] string.asc = A - Z @@ -565,7 +569,7 @@ SSPISeparatorReplacement = Pang-hiwalay SSPIDefaultLanguage = Default na wika CommitSummary = Pangkalahatang-ideya ng commit glob_pattern_error = ` hindi angkop ang glob pattern: %s` -require_error = ` hindi maaring walang laman.` +require_error = ` hindi maaaring walang laman.` alpha_dash_error = ` dapat maglaman lamang ng alphanumeric, dash ("-") at underscore ("_") na mga character.` alpha_dash_dot_error = ` dapat maglaman lamang ng alphanumeric, dash ("-"), underscore ("_") at tuldok (".") na mga character.` git_ref_name_error = ` dapat na mahusay na nabuong pangalan ng Git reference` @@ -613,8 +617,8 @@ unset_password = Hindi nagtakda ng password ang login user. unsupported_login_type = Hindi sinusuportahan ang uri ng pag-login para burahin ang account. user_not_exist = Hindi umiiral ang user. team_not_exist = Hindi umiiral ang koponan. -last_org_owner = Hindi mo maaring tanggalin ang pinakahuling user sa "mga may-ari" na koponan. Kailangan may kahit isang may-ari para sa organisasyon. -cannot_add_org_to_team = Hindi maaring madagdag ang isang organisasyon bilang miyembro ng koponan. +last_org_owner = Hindi mo maaaring tanggalin ang pinakahuling user sa "mga may-ari" na koponan. Kailangan may kahit isang may-ari para sa organisasyon. +cannot_add_org_to_team = Hindi maaaring madagdag ang isang organisasyon bilang miyembro ng koponan. duplicate_invite_to_team = Inimbita na ang user bilang miyembro ng koponan. organization_leave_success = Matagumpay kang umalis sa organisasyon na %s. invalid_ssh_key = Hindi ma-verify ang iyong SSH key: %s @@ -629,7 +633,7 @@ still_own_packages = Ang iyong account ay nagmamay-ari ng isa o higit pang packa org_still_own_repo = Ang organisasyon na ito ay nagmamay-ari ng isa o higit pang mga repositoryo, burahin o ilipat sila muna. org_still_own_packages = Ang organisasyon na ito ay nagmamay-ari ng isa o higit pang mga package, burahin sila muna. target_branch_not_exist = Hindi umiiral ang target branch. -admin_cannot_delete_self = Hindi mo maaring burahin ang sarili mo kapag isa kang tagapangasiwa. Paki-tanggal ang iyong pribilehiyong tagapangasiwa muna. +admin_cannot_delete_self = Hindi mo maaaring burahin ang sarili mo kapag isa kang tagapangasiwa. Paki-tanggal ang iyong pribilehiyong tagapangasiwa muna. required_prefix = Ang input ay dapat magsimula sa "%s" FullName = Buong pangalan Description = Paglalarawan @@ -762,8 +766,8 @@ gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig delete_token_success = Nabura na ang token. Ang mga application na gumagamit nito ay hindi na maa-access ang iyong account. add_email_confirmation_sent = Ang isang email na pang-kumpirma ay ipinadala sa %s. Para kumpirmahin ang iyong email address, pakisuri ang iyong inbox at sundan ang ibinigay na link sa loob ng %s. key_content_ssh_placeholder = Nagsisimula sa "ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ecdsa-sha2-nistp256@openssh.com", o "sk-ssh-ed25519@openssh.com" -gpg_key_verified_long = Na-verify ang key na ito gamit ng isang token at maaring gamitin para i-verify ang mga commit na tumutugma sa anumang mga naka-activate na email address para sa user na ito kasama ang mga tumutugmang pagkakakilanlan para sa key na ito. -ssh_key_verified_long = Ang key na ito ay na-verify gamit ng isang token at maaring gamitin para i-verify ang mga commit na tumutugma na email address para sa user na ito. +gpg_key_verified_long = Na-verify ang key na ito gamit ng isang token at maaaring gamitin para i-verify ang mga commit na tumutugma sa anumang mga naka-activate na email address para sa user na ito kasama ang mga tumutugmang pagkakakilanlan para sa key na ito. +ssh_key_verified_long = Ang key na ito ay na-verify gamit ng isang token at maaaring gamitin para i-verify ang mga commit na tumutugma na email address para sa user na ito. add_principal_success = Idinagdag na ang SSH certificate principal na "%s". ssh_key_deletion_desc = Ang pagtanggal ng SSH key ay matatanggihan ang pag-access sa iyong account. Magpatuloy? no_activity = Walang kamakilang aktibidad @@ -822,7 +826,7 @@ keep_email_private = Itago ang email address openid_desc = Hinahayaan ka ng OpenID na mag-delegate ng pagpapatunay sa isang panlabas na tagabigay ng serbisyo. ssh_desc = Ang mga pampublikong SSH key na ito ay nauugnay sa iyong account. Pinapayagan ng kaukulang pribadong key ang buong pag-access sa iyong mga repositoryo. Ang mga SSH key na na-verify ay maaaring magamit upang mapatunayan ang mga naka-sign na Git commit sa pamamagitan ng SSH. principal_desc = Ang mga SSH principal na ito ay nauugnay sa iyong account at pinapayagan ang buong pag-access sa iyong mga repositoryo. -ssh_helper = Kailangan ng tulong? Tignan ang guide sa paggawa ng sarili mong mga SSH key o ilutas ang mga karaniwang problema na maaring moong matagpo gamit ng SSH. +ssh_helper = Kailangan ng tulong? Tignan ang guide sa paggawa ng sarili mong mga SSH key o ilutas ang mga karaniwang problema na maaaring moong matagpo gamit ng SSH. gpg_helper = Kailangan ng tulong? Tignan ang guide tungkol sa GPG. add_new_key = Magdagdag ng SSH key add_new_gpg_key = Magdagdag ng GPG key @@ -832,7 +836,7 @@ ssh_key_been_used = Idinagdag na ang SSH key na ito sa server. ssh_key_name_used = Ang isang SSH key na may katulad na pangalan ay umiiral na sa iyong account. ssh_principal_been_used = Idinagdag na ang principal na ito sa server. gpg_key_matched_identities = Mga Tumutugma na Pagkakakilanlan: -gpg_key_matched_identities_long = Ang mga naka-embed na pagkakakilanlan sa key na ito ay tumutugma sa mga sumusunod na naka-activate na email address para sa user na ito. Ang mga commit na tumutugma sa mga email address na ito ay maaring i-verify gamit ng key na ito. +gpg_key_matched_identities_long = Ang mga naka-embed na pagkakakilanlan sa key na ito ay tumutugma sa mga sumusunod na naka-activate na email address para sa user na ito. Ang mga commit na tumutugma sa mga email address na ito ay maaaring i-verify gamit ng key na ito. gpg_key_verified = Naka-verify na key gpg_key_verify = I-verify gpg_invalid_token_signature = Ang ibinigay na GPG key, signature, at token ay hindi tumutugma o luma. @@ -984,7 +988,7 @@ manage_account_links_desc = Ang mga panlabas na account na ito ay naka-link sa i hooks.desc = Magdagdag ng mga webhook na mati-trigger para sa lahat ng mga repositoryo na minamay-ari mo. orgs_none = Hindi ka isang miyembro ng anumang mga organisasyon. oauth2_application_create_description = Ang mga OAuth2 application ay pinapayagan ang mga third-party na aplikasyon na i-access ang mga user account sa instansya na ito. -oauth2_application_locked = Ang Forgejo ay pini-pre register ang ibang mga OAuth2 application sa startup kapag naka-enable sa config. Para iwasan ang hindi inaasahang gawain, hindi ito maaring i-edit o tanggalin. Mangyaring sumangguni sa dokumentasyon ng OAuth2 para sa karagdagang impormasyon. +oauth2_application_locked = Ang Forgejo ay pini-pre register ang ibang mga OAuth2 application sa startup kapag naka-enable sa config. Para iwasan ang hindi inaasahang gawain, hindi ito maaaring i-edit o tanggalin. Mangyaring sumangguni sa dokumentasyon ng OAuth2 para sa karagdagang impormasyon. remove_account_link_desc = Ang pagtanggal ng naka-link na account ay babawiin ang pag-access nito sa iyong Forgejo account. Magpatuloy? visibility.public_tooltip = Makikita ng lahat hints = Mga Pahiwatig @@ -1000,11 +1004,31 @@ keep_activity_private.description = Makikita mo lang at mga tagapangasiwa ng ins language.description = Mase-save ang wika sa iyong account at gagamitin bilang default pagkatapos mong mag-log in. language.localization_project = Tulungan kaming isalin ang Forgejo sa iyong wika! Matuto pa. pronouns_custom_label = Mga pasadyang pronoun -user_block_yourself = Hindi mo maaring harangan ang sarili mo. +user_block_yourself = Hindi mo maaaring harangan ang sarili mo. change_username_redirect_prompt.with_cooldown.one = Magiging available ang lumang username sa lahat pagkatapos ng panahon ng cooldown ng %[1]d araw, maari mo pa ring ma-claim muli ang lumang username sa panahon ng panahon ng cooldown. change_username_redirect_prompt.with_cooldown.few = Magiging available ang lumang username sa lahat pagkatapos ng panahon ng cooldown ng %[1]d araw, maari mo pa ring ma-claim muli ang lumang username sa panahon ng panahon ng cooldown. keep_pronouns_private = Ipakita lang ang mga panghalip sa mga naka-authenticate na user keep_pronouns_private.description = Itatago nito ang iyong mga panghalip mula sa mga bisita na hindi naka-log in. +quota.applies_to_user = Nag-aapply ang mga sumusunod na panuntunan ng quota sa iyong account +quota.sizes.assets.attachments.issues = Mga attachment sa isyu +quota.applies_to_org = Ang mga sumusunod na panuntunan sa quota ay nalalapat sa organisasyong ito +storage_overview = Buod ng Storage +quota = Quota +quota.rule.exceeded = Nalampasan +quota.rule.exceeded.helper = Ang kabuuang sukat ng mga bagay para sa panuntunang ito ay lumampas sa quota. +quota.rule.no_limit = Walang limitasyon +quota.sizes.all = Lahat +quota.sizes.repos.all = Mga repositoryo +quota.sizes.repos.public = Mga pampublikong repositoryo +quota.sizes.repos.private = Mga pribadong repositoryo +quota.sizes.git.all = Nilalaman ng Git +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.all = Mga asset +quota.sizes.assets.attachments.all = Mga attachment +quota.sizes.assets.attachments.releases = Mga attachment sa release +quota.sizes.assets.artifacts = Mga artifact +quota.sizes.assets.packages.all = Mga package +quota.sizes.wiki = Wiki [repo] template_description = Ang mga template na repositoryo ay pinapayagan ang mga gumagamit na mag-generate ng mga bagong repositoryo na may magkatulad na istraktura ng direktoryo, mga file, at opsyonal na mga setting. @@ -1017,7 +1041,7 @@ admin.enabled_flags = Mga flag na naka-enable para sa repositoryo: admin.update_flags = I-update ang mga flag admin.flags_replaced = Napalitan ang mga flag ng repositoryo owner = May-ari -owner_helper = Maaring hindi mapapakita ang ibang organisasyon sa dropdown dahil sa pinakamataas na bilang ng repositoryo na limitasyon. +owner_helper = Maaaring hindi mapapakita ang ibang organisasyon sa dropdown dahil sa pinakamataas na bilang ng repositoryo na limitasyon. repo_name = Pangalan ng repositoryo repo_name_helper = Ang mga magandang pangalan ng repositoryo ay gumagamit ng maliit, makakaalala, at unique na mga keyword. repo_size = Laki ng Repositoryo @@ -1033,7 +1057,7 @@ fork_repo = I-fork ang repositoryo fork_from = I-fork mula sa already_forked = Na-fork mo na ang %s fork_to_different_account = Mag-fork sa ibang account -fork_visibility_helper = Ang visibility ng isang naka-fork na repositoryo ay hindi maaring baguhin. +fork_visibility_helper = Ang visibility ng isang naka-fork na repositoryo ay hindi maaaring baguhin. open_with_editor = Buksan gamit ang %s download_bundle = I-download ang BUNDLE repo_gitignore_helper_desc = Piliin kung anong mga file na hindi susubaybayin sa listahan ng mga template para sa mga karaniwang wika. Ang mga tipikal na artifact na ginagawa ng mga build tool ng wika ay kasama sa .gitignore ng default. @@ -1118,7 +1142,7 @@ stars = Mga bitwin migrate_options_mirror_helper = Magiging salamin ang repositoryong ito migrate_options_lfs_endpoint.description.local = Sinusuporta rin ang lokal na server path. editor.this_file_locked = Nakakandado ang file -editor.filename_cannot_be_empty = Hindi maaring walang laman ang pangalan ng file. +editor.filename_cannot_be_empty = Hindi maaaring walang laman ang pangalan ng file. commits.message = Mensahe commits.newer = Mas bago commits.date = Petsa @@ -1175,7 +1199,7 @@ template.avatar = Avatar migrate_options = Mga opsyon sa paglipat migrate.clone_address_desc = Ang HTTP(S) o Git "clone" URL ng umiiral na repositoryo need_auth = Awtorisasyon -migrate.github_token_desc = Maari kang maglagay ng isa o higit pang mga token na hinihiwalay ng kuwit dito upang gawing mas-mabilis ang pagmigrate dahil sa rate limit ng GitHub API. BABALA: Ang pagabuso ng feature na ito ay maaring maglabag sa patakaran ng tagapagbigay ng serbisyo at maaring magdulot ng pag-block ng account. +migrate.github_token_desc = Maaari kang maglagay ng isa o higit pang mga token na hinihiwalay ng kuwit dito upang gawing mas-mabilis ang pagmigrate dahil sa rate limit ng GitHub API. BABALA: Ang pagabuso ng feature na ito ay maaaring maglabag sa patakaran ng tagapagbigay ng serbisyo at maaaring magdulot ng pag-block ng account. template.invalid = Kailangang pumili ng kahit isang template na repositoryo migrate_options_lfs_endpoint.description = Susubukan ng migration na gamitin ang iyong Git remote upang matukoy ang LFS server. Maari mong magtiyak ng custom na endpoint kapag ang LFS data ng repositoryo ay nakalagay sa ibang lugar. blame.ignore_revs.failed = Nabigong hindi pansinin ang mga rebisyon sa .git-blame-ignore-revs. @@ -1208,7 +1232,7 @@ adopt_preexisting_success = Pinagtibay ang mga file at ginawa ang repositoryo mu delete_preexisting_success = Burahin ang mga hindi pinatibay na file sa %s blame_prior = Tignan ang blame bago ang pagbabago na ito migrate.permission_denied = Hindi ka pinapayagang mag-import ng mga lokal na repositoryo. -migrate.permission_denied_blocked = Hindi ka maaring mag-import mula sa mga hindi pinapayagang host, magyaring magtanong sa pangangasiwa na suriin ang ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS na mga setting. +migrate.permission_denied_blocked = Hindi ka maaaring mag-import mula sa mga hindi pinapayagang host, magyaring magtanong sa pangangasiwa na suriin ang ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS na mga setting. migrate.invalid_local_path = Hindi wasto ang lokal na path. Hindi ito umiiral o hindi isang direktoryo. migrate.invalid_lfs_endpoint = Hindi wasto ang LFS endpoint. migrate.migrating_failed = Nabigo ang pag-migrate mula sa %s. @@ -1257,7 +1281,7 @@ file_follow = Sundan ang symlink file_view_source = Tignan ang source file_view_rendered = Tignan ng naka-render ambiguous_runes_header = `Naglalaman ng file na ito ng mga hindi tiyak na Unicode character` -ambiguous_runes_description = `Ang file na ito ay naglalaman ng mga Unicode character na maaring malilito sa ibang mga character. Kung sa tingin mo ay sinasadya ito, maari mong ligtas na hindi pansinin ang babala ito. Gamitin ang I-escape na button para ipakita sila.` +ambiguous_runes_description = `Ang file na ito ay naglalaman ng mga Unicode character na maaaring malilito sa ibang mga character. Kung sa tingin mo ay sinasadya ito, maaari mong ligtas na hindi pansinin ang babala ito. Gamitin ang I-escape na button para ipakita sila.` file_copy_permalink = Kopyahin ang permalink view_git_blame = Tignan ang git blame video_not_supported_in_browser = Hindi sinusuportahan ng inyong browser ang HTML5 "video" tag. @@ -1287,7 +1311,7 @@ broken_message = Ang Git data na pinagbabatayan sa repositoryo na ito ay hindi m file_history = Kasaysayan invisible_runes_header = `Nalalaman ng file na ito ng mga hindi nakikitang Unicode character` file_too_large = Masyadong malaki ang file para ipakita. -invisible_runes_description = `Ang file na ito ay naglalaman ng mga hindi nakikitang Unicode character na hindi nakikilala ng mga tao ngunit maaring maproseso ng ibang paraan ng isang computer. Kung sa tingin mo ay sinasadya ito, maari mong ligtas na hindi pansinin ang babala na ito. Gamitin ang I-escape na button para ipakita sila.` +invisible_runes_description = `Ang file na ito ay naglalaman ng mga hindi nakikitang Unicode character na hindi nakikilala ng mga tao ngunit maaaring maproseso ng ibang paraan ng isang computer. Kung sa tingin mo ay sinasadya ito, maaari mong ligtas na hindi pansinin ang babala na ito. Gamitin ang I-escape na button para ipakita sila.` commit.contained_in_default_branch = Ang commit na ito ay bahagi ng default na branch migrate.migrating_labels = Nililipat ang mga label filter_branch_and_tag = I-filter ang branch o tag @@ -1321,7 +1345,7 @@ clone_this_repo = I-clone ang repositoryo na ito cite_this_repo = Banggitin ang repositoryo na ito create_new_repo_command = Paggawa ng bagong repositoryo sa command line code = Code -ambiguous_character = `Ang %[1]c [U+%04[1]X] ay maaring malito sa %[2]c [U+%04[2]X]` +ambiguous_character = `Ang %[1]c [U+%04[1]X] ay maaaring malito sa %[2]c [U+%04[2]X]` escape_control_characters = I-escape unescape_control_characters = I-unescape invisible_runes_line = `Ang linya na ito ay may mga hindi nakikitang Unicode character` @@ -1335,7 +1359,7 @@ editor.must_be_on_a_branch = Dapat nasa branch ka upang gumawa o magmunkahi ng m editor.new_branch_name_desc = Bagong pangalan ng branch… editor.cancel = Kanselahin issues.role.member = Miyembro -issues.remove_request_review_block = Hindi maaring tanggalin ang hiling sa pagsuri +issues.remove_request_review_block = Hindi maaaring tanggalin ang hiling sa pagsuri issues.edit = Baguhin issues.cancel = Kanselahin issues.save = IImbak @@ -1736,7 +1760,7 @@ issues.label_exclusive_desc = Pangalanan ang label na scope/item up issues.archived_label_description = (Naka-archive) %s issues.label.filter_sort.alphabetically = Ayon sa alpabeto issues.subscribe = Mag-subscribe -issues.max_pinned = Hindi ka maaring mag-pin ng higit pang mga isyu +issues.max_pinned = Hindi ka maaaring mag-pin ng higit pang mga isyu issues.pin_comment = na-pin ito %s issues.unpin_comment = na-unpin ito %s issues.lock = I-lock ang usapan @@ -1792,7 +1816,7 @@ wiki.last_commit_info = Binago ni %s ang pahinang ito %s issues.content_history.edited = binago issues.ref_pull_from = `isinangguni ang hiling sa paghila na ito %[4]s %[2]s` pulls.merged_title_desc_few = isinali ang %[1]d mga commit mula sa %[2]s patungong %[3]s %[4]s -settings.org_not_allowed_to_be_collaborator = Hindi maaring idagdag ang mga organizasyon bilang tagatulong. +settings.org_not_allowed_to_be_collaborator = Hindi maaaring idagdag ang mga organisasyon bilang tagatulong. settings.add_collaborator_success = Naidagdag ang tagatulong. settings.federation_following_repos = Mga URL ng Mga Sinusundang Repositoryo. Hinihiwalay ng ";", walang whitespace. diff.comment.reply = Tumugon @@ -1891,8 +1915,8 @@ issues.dependency.remove_info = Tanggalin ang dependency na ito issues.dependency.added_dependency = `nagdagdag ng bagong dependency %s` issues.review.dismissed_label = Nadismiss issues.review.dismissed = nadismiss ang pagsuri ni %s %s -issues.review.self.approval = Hindi mo maaring aprubahan ang sarili mong hiling sa paghila. -issues.review.self.rejection = Hindi mo maaring humiling ng pagbabago sa sarili mong hiling sa paghila. +issues.review.self.approval = Hindi mo maaaring aprubahan ang sarili mong hiling sa paghila. +issues.review.self.rejection = Hindi mo maaaring humiling ng pagbabago sa sarili mong hiling sa paghila. pulls.nothing_to_compare_have_tag = Magkapareho ang mga piniling branch/tag. issues.dependency.no_permission_1 = Wala kang pahintulot na basahin ang dependency na %d issues.dependency.no_permission_n = Wala kang pahintulot na basahin ang mga %d dependency @@ -1926,7 +1950,7 @@ activity.active_prs_count_n = %d aktibong mga hiling sa paghila issues.author.tooltip.issue = May-akda ng iysung ito ang user. issues.author.tooltip.pr = May-akda ng hiling sa paghila na ito ang user na ito. issues.dependency.add_error_dep_exists = Umiiral na and dependency. -issues.dependency.add_error_cannot_create_circular = Hindi ka maaring gumawa ng dependency na may dalawang isyu na humaharang sa isa't isa. +issues.dependency.add_error_cannot_create_circular = Hindi ka maaaring gumawa ng dependency na may dalawang isyu na humaharang sa isa't isa. issues.dependency.add_error_same_issue = Hindi mo magagwang dumepende ang isyu sa sarili. issues.dependency.add_error_dep_not_same_repo = Dapat nasa katulad na repositoryo ang mga isyu. issues.dependency.add_error_dep_issue_not_exist = Hindi umiiral ang dumedependeng isyu. @@ -1945,7 +1969,7 @@ issues.review.show_resolved = Ipakita ang naresolba issues.review.hide_resolved = Itago ang naresolba issues.review.resolve_conversation = Iresolba ang paguusap issues.review.un_resolve_conversation = I-unresolve ang paguusap -issues.blocked_by_user = Hindi ka maaring gumawa ng isyu sa repositoryo na ito dahil hinarang ka ng may-ari ng repositoryo. +issues.blocked_by_user = Hindi ka maaaring gumawa ng mga isyu sa repositoryong ito dahil hinarang ka ng may-ari ng repositoryo. issues.review.show_outdated = Ipakita ang luma issues.review.hide_outdated = Itago ang luma issues.review.resolved_by = minarkahan ang paguusap na ito bilang naresolba @@ -1963,7 +1987,7 @@ wiki.wiki_page_revisions = Mga rebisyon ng pahina settings.federation_not_enabled = Hindi naka-enable ang federation sa instansya na ito. pulls.filter_changes_by_commit = I-filter ayon sa commit settings.githooks = Mga Git hook -issues.comment.blocked_by_user = Hindi ka makakagawa ng komento sa isyu na ito dahil hinarang ka ng may-ari ng repositoryo o ng gumawa ng isyu. +issues.comment.blocked_by_user = Hindi ka maaaring gumawa ng komento sa isyu na ito dahil hinarang ka ng may-ari ng repositoryo o ng gumawa ng isyu. pulls.view = Tignan ang hiling sa paghila activity.navbar.contributors = Mga contributor activity.navbar.recent_commits = Mga kamakailang commit @@ -2042,7 +2066,7 @@ pulls.wrong_commit_id = ang commit id ay dapat ang commit id sa patutunguhan na pulls.blocked_by_changed_protected_files_1 = Hinarangan ang hiling sa paghila na ito dahil nagbabago ito ng isang nakaprotektang file: pulls.blocked_by_changed_protected_files_n = Hinarangan ang hiling sa paghila na ito dahil nagbabago ito ng mga nakaprotektang file: pulls.blocked_by_official_review_requests = Hinarangan ang hiling sa paghila na ito dahil may nawawalang pag-apruba mula sa isa o higit pang mga opisyal na tagasuri. -pulls.can_auto_merge_desc = Maaring isama ng awtomatiko ang hiling sa paghila na ito. +pulls.can_auto_merge_desc = Maaaring isama ng awtomatiko ang hiling sa paghila na ito. pulls.num_conflicting_files_n = %d mga magkasalungat na file pulls.num_conflicting_files_1 = %d magkasalungat na file issues.review.add_review_requests = humiling ng mga pagsuri mula sa %[1]s %[2]s @@ -2322,7 +2346,7 @@ settings.convert_confirm = I-convert ang repositoryo settings.webhook.replay.description_disabled = Para i-replay ang webhook na ito, i-activate ito. settings.tracker_issue_style.regexp = Regular na Ekspresyon settings.admin_stats_indexer = Taga-index ng istatistika ng code -pulls.open_unmerged_pull_exists = `Hindi ka maaring gumawa ng pagbukas-muli na operasyon dahil may nakabinbin na hiling sa paghila (#%d) na may magkatulad na katangian.` +pulls.open_unmerged_pull_exists = `Hindi ka maaaring gumawa ng pagbukas-muli na operasyon dahil may nakabinbin na hiling sa paghila (#%d) na may magkatulad na katangian.` milestones.deletion_success = Binura na ang milestone. pulls.auto_merge_has_pending_schedule = Naiskedyul ni %[1]s na awtomatiko na isama ang hiling sa paghila na ito kapag magtagumpay ang lahat ng mga pagsusuri %[2]s. issues.summary_card_alt = Pangkalahatang-ideyang card ng isyu na tawag na "%s" sa repositoryong %s @@ -2390,9 +2414,9 @@ settings.external_wiki_url_desc = Ire-redirect ang mga bisita sa URL ng panlabas settings.use_external_issue_tracker = Gumamit ng panlabas na tagasubaybay na isyu settings.wiki_desc = I-enable ang wiki ng repositoryo settings.trust_model.default.desc = Gamitin ang default na modelo ng pagkatiwala ng repositoryo para sa installation na ito. -settings.add_webhook.invalid_path = Hindi maaring maglaman ang path ng parte na "." o ".." o walang laman na string. Hindi maaring magsimula o magtapos sa slash. +settings.add_webhook.invalid_path = Hindi maaaring maglaman ang path ng parte na "." o ".." o walang laman na string. Hindi maaaring magsimula o magtapos sa slash. settings.webhook_deletion = Tanggalin ang webhook -settings.add_webhook.invalid_channel_name = Hindi maaring walang laman ang pangalan ng channel ng webhook at hindi maaring maglaman lang ng # na character. +settings.add_webhook.invalid_channel_name = Hindi maaaring walang laman ang pangalan ng channel ng webhook at hindi maaaring maglaman lang ng # na character. pulls.update_branch = I-update ang branch sa pamamagitan ng pagsama pulls.status_checks_show_all = Ipakita ang lahat ng mga pagsusuri pulls.cmd_instruction_checkout_title = I-checkout @@ -2445,8 +2469,8 @@ settings.mirror_settings.docs.doc_link_title = Paano ako mag-mirror ng mga repos settings.pull_mirror_sync_quota_exceeded = Nalagpasan ang quota, hindi hihila ng mga pagbabago. settings.mirror_settings.push_mirror.none_ssh = Wala settings.mirror_settings.push_mirror.copy_public_key = Kopyahin ang publikong key -pulls.delete_after_merge.head_branch.is_protected = Ang head branch na gusto mong burahin ay isang pinoprotektahang branch at hindi maaring burahin. -pulls.delete_after_merge.head_branch.is_default = Ang head branch na gusto mong burahin ay ang default branch at hindi maaring burahin. +pulls.delete_after_merge.head_branch.is_protected = Ang head branch na gusto mong burahin ay isang pinoprotektahang branch at hindi maaaring burahin. +pulls.delete_after_merge.head_branch.is_default = Ang head branch na gusto mong burahin ay ang default branch at hindi maaaring burahin. issues.num_reviews_few = %d mga pagsusuri issues.num_reviews_one = %d pagsusuri diff.image.swipe = I-swipe @@ -2469,7 +2493,7 @@ diff.comment.add_single_comment = Magdagdag ng iisang komento diff.comment.placeholder = Mag-iwan ng komento release.detail = Mga detalye sa release release.tags = Mga tag -release.title_empty = Hindi maaring walang laman ang paksa. +release.title_empty = Hindi maaaring walang laman ang paksa. branch.included_desc = Ang branch na ito ay kabilang ng default branch release.source_code = Source code release.edit_subheader = Inaayos ng mga release ang mga bersyon ng proyekto. @@ -2512,8 +2536,8 @@ settings.block_outdated_branch_desc = Hindi magiging posible ang pagsasama kung settings.block_rejected_reviews_desc = Hindi magiging posible ang pagsasama kapag may mga hiniling ng pagbabago ang mga opisyal na tagasuri, kahit na may sapat na pagapruba. settings.block_on_official_review_requests = Harangan ang merge sa opisyal na hiling sa pagsuri settings.tags.protection.allowed = Pinapayagan -settings.lfs_delete_warning = Ang pagbura ng LFS file ay maaring magdulot ng mga "object does not exist" na error sa checkout. Sigurado ka ba? -settings.protected_branch_required_approvals_min = Hindi maaring negatibo ang mga kinakailangang pagapruba. +settings.lfs_delete_warning = Ang pagbura ng LFS file ay maaaring magdulot ng mga "object does not exist" na error sa checkout. Sigurado ka ba? +settings.protected_branch_required_approvals_min = Hindi maaaring negatibo ang mga kinakailangang pagapruba. settings.lfs_lock_path = File path na kakandaduhin… settings.lfs_force_unlock = Pilitin ang pag-unlock settings.lfs_pointers.accessible = Naa-access ng user @@ -2524,7 +2548,7 @@ settings.protect_merge_whitelist_users = Mga naka-whitelist na user para sa pags settings.protect_merge_whitelist_teams = Mga naka-whitelist na koponan para sa pagsasama settings.protect_check_status_contexts = I-enable ang pagsusuri ng estado settings.protect_status_check_patterns = Mga pattern sa pagsusuri ng estado -settings.protect_status_check_patterns_desc = Ilagay ang mga pattern para i-specify kung aling mga pagsusuri ng estado na kailangang magpasa bago maisama ang mga branch sa isang branch na tumutugma sa rule na ito. Nagse-specify ang bawat linya ng pattern. Hindi maaring walang laman ang mga pattern. +settings.protect_status_check_patterns_desc = Ilagay ang mga pattern para i-specify kung aling mga pagsusuri ng estado na kailangang magpasa bago maisama ang mga branch sa isang branch na tumutugma sa rule na ito. Nagse-specify ang bawat linya ng pattern. Hindi maaaring walang laman ang mga pattern. settings.protect_check_status_contexts_list = Mga pagsusuri ng estado na nahanap sa huling linggo para sa repositoryo na ito diff.generated = na-generate branch.confirm_create_branch = Gumawa ng branch @@ -2674,9 +2698,9 @@ settings.protected_branch_deletion_desc = Ang pag-disable ng branch protection a settings.rename_branch_failed_protected = Hindi mababago ang pangalan ng branch na %s dahil ito ay isang nakaprotektang branch. editor.add_tmpl.filename = Pangalan ng file settings.protect_approvals_whitelist_users = Mga naka-whitelist na tagasuri -settings.protect_protected_file_patterns_desc = Ang mga nakaprotektang file ay hindi pinapayagan na direktang mabago kahit na may karapatan ang user na magdagdag, i-edit, o burahin ang mga file sa branch na ito. Ang mga maraming pattern ay maaring mahiwalay gamit ng semicolon (";"). Tignan ang %[2]s na dokumentasyon para sa pattern syntax. Mga halimbawa: .drone.yml, /docs/**/*.txt. -branch.delete_branch_has_new_commits = Hindi maaring burahin ang branch na "%s" dahil may mga bagong commit na nadagdag matapos ang pagsasama. -settings.protect_unprotected_file_patterns_desc = Ang mga hindi nakaprotektang file ay pinapayagan na direktang mabago kung may write access ang user, bina-bypass ang restriction ng pagtulak. Ang mga maraming pattern ay maaring mahiwalay gamit ng semicolon (";"). Tignan ang %[2]s na dokumentasyon para sa pattern syntax. Mga halimbawa: .drone.yml, /docs/**/*.txt. +settings.protect_protected_file_patterns_desc = Ang mga nakaprotektang file ay hindi pinapayagan na direktang mabago kahit na may karapatan ang user na magdagdag, i-edit, o burahin ang mga file sa branch na ito. Ang mga maraming pattern ay maaaring mahiwalay gamit ng semicolon (";"). Tignan ang %[2]s na dokumentasyon para sa pattern syntax. Mga halimbawa: .drone.yml, /docs/**/*.txt. +branch.delete_branch_has_new_commits = Hindi maaaring burahin ang branch na "%s" dahil may mga bagong commit na nadagdag matapos ang pagsasama. +settings.protect_unprotected_file_patterns_desc = Ang mga hindi nakaprotektang file ay pinapayagan na direktang mabago kung may write access ang user, bina-bypass ang restriction ng pagtulak. Ang mga maraming pattern ay maaaring mahiwalay gamit ng semicolon (";"). Tignan ang %[2]s na dokumentasyon para sa pattern syntax. Mga halimbawa: .drone.yml, /docs/**/*.txt. settings.no_protected_branch = Walang mga nakaprotekta na branch. settings.protected_branch_required_rule_name = Kinakailangan na pangalan ng rule settings.protected_branch_duplicate_rule_name = Mayroon nang rule para sa set ng mga branch na ito @@ -2692,7 +2716,7 @@ branch.branch_name_conflict = Sumasalungat ang pangalan ng branch na "%s" sa umi branch.protected_deletion_failed = Nakaprotekta ang branch na "%s". Hindi ito mabubura. diff.file_after = Pagkatapos release.deletion_tag_desc = Buburahin ang tag na ito sa repositoryo. Mapapanatiling hindi nabago ang nilalaman at kasaysayan ng repositoryo. Magpatuloy? -topic.format_prompt = Dapat magsimula ang mga topic ng numero o letra, maaring magsama ng mga dash ("-") at dot ("."), maaring hanggang sa 35 na character na haba. Kailangang lowercase ang mga character. +topic.format_prompt = Dapat magsimula ang mga topic ng numero o letra, maaaring magsama ng mga dash ("-") at dot ("."), maaaring hanggang sa 35 na character na haba. Kailangang lowercase ang mga character. branch.new_branch_from = Gumawa ng bagong branch mula sa "%s" error.csv.unexpected = Hindi ma-render ang file na ito dahil naglalaman ito ng hindi inaasahang character sa linyang %d at column %d. settings.ignore_stale_approvals_desc = Huwag ibilang ang mga pagapruba na ginawa sa mga lumang commit (mga lipas na pagsusuri) sa kung gaano karaming pagapruba ang mayroon sa PR na ito. Walang kinalaman kung ang mga lipas na pagsusuri ay na-dismiss na. @@ -2735,6 +2759,10 @@ archive.pull.noreview = Naka-archive ang repositoryong ito. Hindi ka makakasuri commits.view_single_diff = Tignan ang mga pagbabago sa file na ito na ipinakilala sa commit na ito pulls.editable = Nababago pulls.editable_explanation = Pinapayagan ng hiling sa paghila na ito ang mga pagbabago mula sa mga tagapangasiwa. Maaari kang direktang mag-ambag dito. +issues.reopen.blocked_by_user = Hindi mo maaaring buksan muli ang isyung ito dahil hinarang ka ng may-ari ng repositoryo o ng may-akda ng isyung ito. +pulls.comment.blocked_by_user = Hindi ka maaaring magkomento sa hiling sa paghila na ito dahil hinarang ka ng may-ari ng repositoryo o ng may-akda ng hiling sa paghila. +issues.filter_no_results = Walang mga resulta +issues.filter_no_results_placeholder = Subukang ayusin ang iyong mga filter sa paghahanap. [search] commit_kind = Maghanap ng mga commit… @@ -2845,11 +2873,11 @@ users.repos = Mga Repo users.send_register_notify = Abisuhan tungkol sa pagrehistro sa pamamagitan ng email users.is_admin = Tagapangasiwa na account users.is_restricted = Pinaghihigpitang account -users.allow_import_local = Maaring mag-import ng mga lokal na repositoryo +users.allow_import_local = Maaaring mag-import ng mga lokal na repositoryo users.allow_create_organization = Makakagawa ng mga organisasyon users.update_profile = I-update ang user account users.delete_account = Burahin ang user account -users.cannot_delete_self = Hindi mo maaring burahin ang sarili mo +users.cannot_delete_self = Hindi mo maaaring burahin ang sarili mo users.still_own_repo = Ang user na ito ay nagmamay-ari pa ng isa o higit pang mga repositoryo. Burahin o ilipat sila muna. users.list_status_filter.is_active = Aktibo users.list_status_filter.not_active = Hindi aktibo @@ -3120,7 +3148,7 @@ config.allow_dots_in_usernames = Payagan ang mga user na gumamit ng mga dot sa k config.https_only = HTTPS lamang auths.tip.github = Magrehistro ng bagong OAuth application sa %s auths.tip.gitlab_new = Magrehistro ng bagong application sa %s -emails.delete_primary_email_error = Hindi mo maaring burahin ang pangunahing email. +emails.delete_primary_email_error = Hindi mo maaaring burahin ang pangunahing email. config.provider_config = Config ng provider config.cache_test_slow = Matagumpay ang pagsubok ng cache, ngunit mabagal ang tugon: %s. config.picture_config = Configuration ng larawan at avatar @@ -3271,7 +3299,7 @@ config.cookie_name = Pangalan ng cookie config.gc_interval_time = Oras ng pagitan ng GC config.cookie_life_time = Lifetime ng cookie config.git_clone_timeout = Timeout ng operasyon na pag-clone -monitor.process.cancel_desc = Ang pagkansela ng proseso ay maaring magdulot ng pagkawalan ng data +monitor.process.cancel_desc = Ang pagkansela ng proseso ay maaaring magdulot ng pagkawalan ng data monitor.queue.name = Pangalan auths.oauth2_required_claim_value_helper = Itakda ang value na ito upang i-restrict ang pag-login mula sa pinagmulang ito sa mga user na may claim na may ganitong pangalan at value auths.tip.bitbucket = Magrehistro ng bagong OAuth consumer sa %s at idagdag ang pahintulot na "Account" - "Read" @@ -3358,7 +3386,7 @@ members.member = Miyembro members.private_helper = Gawing visible settings.location = Lokasyon settings.update_setting_success = Nabago na ang mga setting ng organisasyon. -teams.can_create_org_repo_helper = Maaring gumawa ang mga miyembro ng mga bagong repositoryo sa organisasyon. Magkakaroon ng tagapangasiwa na access ang tagagawa sa bagong repositoryo. +teams.can_create_org_repo_helper = Maaaring gumawa ang mga miyembro ng mga bagong repositoryo sa organisasyon. Magkakaroon ng tagapangasiwa na access ang tagagawa sa bagong repositoryo. settings.change_orgname_prompt = Tandaan: Ang pagpalit ng pangalan ng organisasyon ay papalitan din ang URL ng organisasyon at mapapalaya ang lumang pangalan. settings.labels_desc = Magdagdag ng mga label na magagamit sa mga isyu para sa lahat ng mga repositoryo sa ilalim ng organisasyon. members.public_helper = Gawing nakatago @@ -3583,7 +3611,7 @@ search_in_external_registry = Maghanap sa %s alt.registry = I-setup ang registry na ito mula sa command line: alt.registry.install = Para i-install ang package na ito, patakbuhin ang sumusunod na command: alt.install = I-install ang package -alt.setup = Idagdag ang repositoryo sa listahan ng mga nakakonektang repositoryo (piliin ang kinakailangang architechture sa halip ng '_arch_'): +alt.setup = Idagdag ang repositoryo sa listahan ng mga nakakonektang repositoryo (piliin ang kinakailangang architechture sa halip ng "_arch_"): alt.repository = Info ng repositoryo alt.repository.architectures = Mga architechture alt.repository.multiple_groups = Available ang package na ito sa iba't ibang grupo. @@ -3641,7 +3669,7 @@ variables.deletion.description = Permanente ang pagtanggal ng isang variable at status.running = Tumatakbo runners.new_notice = Paano magsimula ng runner runners.update_runner_success = Matagumpay na na-update ang runner -runners.delete_runner_notice = Kapag may trabaho na tumatakbo sa runner na ito, titigilan ito at mamarkahan bilang nabigo. Maaring sirain ang building workflow. +runners.delete_runner_notice = Kapag may trabaho na tumatakbo sa runner na ito, titigilan ito at mamarkahan bilang nabigo. Maaaring sirain ang building workflow. runners.none = Walang mga available na runner runs.status_no_select = Lahat ng status runs.empty_commit_message = (walang laman na mensahe ng commit) @@ -3781,7 +3809,7 @@ error.unit_not_allowed = Hindi ka pinapayagang ma-access ang seksyon ng reposito [dropzone] default_message = I-drop ang mga file o mag-click dito para mag-upload. -invalid_input_type = Hindi ka maaring mag-upload ng mga file sa uri na ito. +invalid_input_type = Hindi ka maaaring mag-upload ng mga file sa uri na ito. file_too_big = Ang laki ng file ({{filesize}}) MB) ay lumalagpas sa pinakamataas na size na ({{maxFilesize}} MB). remove_file = Tanggalin ang file diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index b7372e12f2..d2d960b627 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -19,8 +19,8 @@ language = Teanga notifications = Fógraí active_stopwatch = Rianaitheoir Ama Gníomhach tracked_time_summary = Achoimre ar an am rianaithe bunaithe ar scagairí an liosta eisiúna -create_new = Cruthaigh... -user_profile_and_more = Próifíl agus Socruithe... +create_new = Cruthaigh… +user_profile_and_more = Próifíl agus Socruithe… signed_in_as = Sínithe isteach mar enable_javascript = Éilíonn JavaScript ar an suíomh Gréasáin seo. toc = Tábla na nÁbhar diff --git a/options/locale/locale_gl.ini b/options/locale/locale_gl.ini index 6583eca499..75763775eb 100644 --- a/options/locale/locale_gl.ini +++ b/options/locale/locale_gl.ini @@ -187,6 +187,9 @@ buttons.switch_to_legacy.tooltip = Utilizar o editor herdado buttons.new_table.tooltip = Engadir táboa table_modal.header = Engadir táboa table_modal.placeholder.header = Cabeceira +link_modal.header = Engadir ligazón +link_modal.url = Url +link_modal.description = Descrición [search] @@ -221,6 +224,7 @@ platform = Multiplataforma app_desc = Um servizo Git autoxestionado e fácil de usar install = Fácil de instalar install_desc = Simplemente executa o binario para a túa plataforma, envíao con Docker ou consígueo empaquetado. +license = Código aberto [error] occurred = Ocorreu un erro @@ -264,4 +268,26 @@ sqlite_helper = Ruta do ficheiro para a base de datos SQLite3.
Introduza unha reinstall_confirm_message = A reinstalación cunha base de datos Forgejo existente pode causar varios problemas. Na maioría dos casos, deberías usar o teu "app.ini" existente para executar Forgejo. Se sabes o que estás facendo, confirma o seguinte: reinstall_confirm_check_1 = É posible que se perdan os datos cifrados pola SECRET_KEY en app.ini: é posible que os usuarios non poidan iniciar sesión con 2FA/OTP e que os espellos non funcionen correctamente. Ao marcar esta caixa, confirmas que o ficheiro app.ini actual contén a SECRET_KEY correcta. disable_gravatar.description = Desactiva o uso de Gravatar ou outras fontes de avatares de terceiros. As imaxes predeterminadas utilizaranse para os avatares dos usuarios a menos que carguen o seu propio avatar na instancia. -federated_avatar_lookup = Activar avatares federados \ No newline at end of file +federated_avatar_lookup = Activar avatares federados +repo_path = Ruta raíz do repositorio +run_user = O usuario co que executar +password = Contrasinal +repo_path_helper = Os repositorios Git remotos gardaránse neste directorio. +lfs_path = Ruta raíz de Git LFS +install = Instalación +db_title = Configuración da base de datos +db_name = Nome da base de datos +db_schema = Esquema +db_schema_helper = Déixao baleiro para a base de datos por defecto ("public"). +app_name = Título da instancia +user = Nome de usuario +general_title = Opcións xerais +app_name_helper = Escribe o nome da túa instancia aqui. Será amosado en cada páxina. +mailer_user = Usuario SMTP +mailer_password = Contrasinal SMTP +title = Configuración inicial +db_type = Tipo de base de datos +app_slogan = Slogan da instancia +app_slogan_helper = Escribe o slogan da túa instancia aqui. Ou deixao baleiro para desabilitala. +domain = Dominio do servidor +ssh_port = Porto do servidor SSH \ No newline at end of file diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index e01064588f..dade632016 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -7,7 +7,7 @@ logo=Logo sign_in=Pieteikties sign_in_with_provider=Pieteikties ar %s sign_in_or=vai -sign_out=Attekties +sign_out=Atteikties sign_up=Reģistrēties link_account=Sasaistīt kontu register=Reģistrēties @@ -162,7 +162,7 @@ filter.not_archived = Nav arhivētas filter.is_fork = Atzarojumi filter.not_fork = Nav atzarojumi filter.is_mirror = Spoguļglabātavas -filter.public = Publiskas +filter.public = Atklātas filter.private = Privātas filter.clear = Notīrīt atlasi confirm_delete_artifact = Vai tiešām izdzēst artefaktu '%s'? @@ -206,6 +206,10 @@ buttons.indent.tooltip = Pārvietot vienumus vienu līmeni dziļāk buttons.unindent.tooltip = Pārvietot vienumus vienu līmeni augstāk buttons.new_table.tooltip = Pievienot tabulu table_modal.header = Pievienot tabulu +link_modal.header = Pievienot saiti +link_modal.url = URL +link_modal.description = Apraksts +link_modal.paste_reminder = Norāde: starpliktuvē esošu URL var ielīmēt uzreiz redaktorā, lai izveidotu saiti. [filter] string.asc=A - Z @@ -298,7 +302,7 @@ disable_gravatar.description=Atspējot Gravatar un citu trešo pušu attēlu avo federated_avatar_lookup=Iespējot apvienotos profila attēlus federated_avatar_lookup.description=Uzmeklēt profila attēlus ar Libravatar. disable_registration=Atspējot pašreģistrēšanos -disable_registration.description=Tikai servera pārvaldītāji varēs izveidot jaunus lietotāju kontus. Ir ļoti ieteicams reģistrēšanos paturēt atspējotu, ja vien nav iecerēts mitināt visiem pieejamu publisku serveri un ir gatavība tikt galā ar lielu negodprātīgu kontu skaitu. +disable_registration.description=Tikai servera pārvaldītāji varēs izveidot jaunus lietotāju kontus. Ir ļoti ieteicams reģistrēšanos paturēt atspējotu, ja vien nav iecerēts mitināt visiem pieejamu serveri un ir gatavība tikt galā ar lielu negodprātīgu kontu skaitu. allow_only_external_registration.description=Lietotāji varēs izveidot jaunos kontus tikai izmantojot konfigurētus ārējos pakalpojumus. openid_signin=Iespējot pieteikšanos ar OpenID openid_signin.description=Ļaut lietotājiem pieteikties ar OpenID. @@ -370,9 +374,9 @@ show_only_archived=Attēlot tikai arhivētos show_only_unarchived=Attēlot tikai nearhivētos show_private=Privāts -show_both_private_public=Attēlot gan publiskos, gan privātos +show_both_private_public=Rāda gan atklātās, gan privātās show_only_private=Attēlot tikai privātos -show_only_public=Attēlot tikai publiskos +show_only_public=Attēlo tikai atklātās issues.in_your_repos=Manās glabātavās @@ -470,7 +474,7 @@ authorize_title=Pilnvarot "%s" piekļuvi Tavam kontam? authorization_failed=Pilnvarošana neizdevās authorization_failed_desc=Pilnvarošana neizdevās, jo tika noteikts nederīgs pieprasījums. Lūgums sazināties ar lietotnes, no kuras tika veikts pilnvarošanas pieprasījums, uzturētāju. sspi_auth_failed=SSPI autentifikācija neizdevās -password_pwned=Izvēlētā parole ir nozagto paroļu sarakstā, kas iepriekš ir atklāts publiskās datu noplūdēs. Lūgums mēģināt vēlreiz ar citu paroli un apsvērt to nomainīt arī citur. +password_pwned=Izvēlētā parole ir nozagto paroļu sarakstā, kas iepriekš ir atklāts pieejamās datu noplūdēs. Lūgums mēģināt vēlreiz ar citu paroli un apsvērt to nomainīt arī citur. password_pwned_err=Neizdevās pabeigt pieprasījumu uz HaveIBeenPwned back_to_sign_in = Atpakaļ uz pieteikšanos unauthorized_credentials = Pieteikšanās dati ir nepareizi vai ir izbeigušies. Jāizpilda komanda atkārtoti vai jāizmanto %s, lai iegūtu vairāk informācijas @@ -686,7 +690,7 @@ email_domain_is_not_allowed = Lietotāja e-pasta adreses %s domēna vārd change_avatar=Mainīt profila attēlu… joined_on=Pievienojās %s repositories=Glabātavas -activity=Publiskie notikumi +activity=Atklāti notikumi followers_few=%d sekotāji starred=Izlasei pievienotās glabātavas watched=Vērotās glabātavas @@ -745,7 +749,7 @@ organization=Apvienības uid=UID webauthn=Divpakāpju pieteikšanās (drošības atslēgas) -public_profile=Publiskais profils +public_profile=Visiem pieejamais profils biography_placeholder=Pastāsti citiem mazliet par sevi! (Tiek atbalstīts Markdown) location_placeholder=Kopīgot savu aptuveno atrašanās vietu ar citiem profile_desc=Par Tevi @@ -859,7 +863,7 @@ add_new_principal=Pievienot identitāti ssh_key_been_used=Šī SSH atslēga jau ir pievienota šajā serverī. ssh_key_name_used=SSH atslēga ar tādu pašu nosaukumu jau ir kontā. ssh_principal_been_used=Šāda identitāte jau ir pievienota šājā serverī. -gpg_key_id_used=Jau pastāv publiskā GPG atslēga ar tādu pašu identifikatoru. +gpg_key_id_used=Jau pastāv publiska GPG atslēga ar tādu pašu identifikatoru. gpg_no_key_email_found=Šī GPG atslēga neatbilst nevienai ar kontu saistītajai e-pasta adresei. To joprojām var pievienot, ja tiek parakstīta norādītā pilnvara. gpg_key_matched_identities=Atbilstošās identitātes: gpg_key_matched_identities_long=Šajā atslēgā iegultās identitātes atbilst zemāk uzskaitītājām aktivētajām šī lietotāja e-pasta adresēm. Iesūtījumus, kas atbilst šīm e-pasta adresēm, var apliecināt ar šo atslēgu. @@ -936,8 +940,8 @@ access_token_deletion_confirm_action=Dzēst access_token_deletion_desc=Pilnvaras izdzēšana atsauks lietotņu, kas to izmanto, piekļuvi kontam. Šo darbību nevar atsaukt. Turpināt? delete_token_success=Pilnvara tika izdzēsta. Lietotnēm, kas to izmanto, vairs nav piekļuves kontam. repo_and_org_access=Glabātavas un apvienības piekļuve -permissions_public_only=Tikai publiskās -permissions_access_all=Visas (publiskā, privātās un ierobežotās) +permissions_public_only=Tikai atklātās +permissions_access_all=Visas (atklātās, privātās un ierobežotās) select_permissions=Atlasīt atļaujas permission_no_access=Nav piekļuves permission_read=Lasīt @@ -1031,14 +1035,14 @@ email_notifications.submit=Iestatīt e-pasta iestatījumus email_notifications.andyourown=Un manus paziņojumus visibility=Lietotāja redzamība -visibility.public=Publiska +visibility.public=Atklāta visibility.public_tooltip=Redzams ikvienam visibility.limited=Ierobežota visibility.limited_tooltip=Redzams tikai lietotājiem, kuri ir pieteikušies visibility.private=Privāta visibility.private_tooltip=Redzams tikai apvienību, kurās pievienojies, dalībniekiem change_password = Mainīt paroli -keep_activity_private.description = Tavas publiskās darbības būs redzamas tikai Tev un servera pārvaldītājiem. +keep_activity_private.description = Tavas atklātās darbības būs redzamas tikai Tev un servera pārvaldītājiem. update_hints = Atjaunināt norādes update_hints_success = Norādes tika atjauninātas. user_block_success = Lietotājs tika sekmīgi liegts. @@ -1061,6 +1065,26 @@ change_username_redirect_prompt.with_cooldown.one = Vecais lietotājvārds būs change_username_redirect_prompt.with_cooldown.few = Vecais lietotājvārds būs pieejams visiem pēc noilguma, kas ir %[1]d dienas. Šajā laikā ir iespējams to atkal sākt izmantot. keep_pronouns_private = Vietniekvārdus rādīt tikai lietotājiem, kuri ir pieteikušies keep_pronouns_private.description = Šis paslēps vietniekvārdus no apmeklētājiem, kuri nav pieteikušies. +quota.sizes.assets.all = Līdzekļi +quota.sizes.git.lfs = Git LFS +quota.applies_to_user = Uz kontu attiecas zemāk esošās ierobežojuma kārtulas +quota.rule.exceeded.helper = Kopējais šīs kārtulas objektu izmērs pārsniedz ierobežojumu. +quota.sizes.git.all = Git saturs +quota.rule.exceeded = Pārsniegts +quota.sizes.assets.attachments.all = Pielikumi +quota.sizes.assets.attachments.issues = Pieteikumu pielikumi +quota.sizes.assets.attachments.releases = Laidienu pielikumi +quota.sizes.assets.artifacts = Artefakti +quota.sizes.assets.packages.all = Pakotnes +quota.sizes.wiki = Vikivietne +storage_overview = Krātuves pārskats +quota = Ierobežojums +quota.applies_to_org = Uz apvienību attiecas zemāk esošās ierobežojuma kārtulas +quota.rule.no_limit = Neierobežots +quota.sizes.all = Viss +quota.sizes.repos.all = Glabātavas +quota.sizes.repos.public = Atklātās glabātavas +quota.sizes.repos.private = Privātās glabātavas [repo] new_repo_helper=Glabātava satur visas projekta datnes, tajā skaitā izmaiņu vēsturi. Jau tiek izmantota kaut kur citur? Pārcelt glabātavu. @@ -1167,7 +1191,7 @@ transfer.no_permission_to_accept=Nav atļaujas pieņemt šo nodošanu. transfer.no_permission_to_reject=Nav atļaujas noraidīt šo nodošanu. desc.private=Privāts -desc.public=Publisks +desc.public=Atklāts desc.template=Sagatave desc.internal=Iekšējs desc.archived=Arhivēts @@ -1513,7 +1537,7 @@ issues.choose.open_external_link=Atvērt issues.choose.blank=Noklusējuma issues.choose.blank_about=Izveidot pieteikumu no noklusējuma sagataves. issues.choose.ignore_invalid_templates=Kļūdainās sagataves tika izlaistas -issues.choose.invalid_templates=atrasta(s) %v ķļūdaina(s) sagatave(s) +issues.choose.invalid_templates=atrasta(s) %v nederīgas(s) sagatave(s) issues.choose.invalid_config=Pieteikumu konfigurācija satur kļūdas: issues.no_ref=Nav norādīts zars/birka issues.create=Izveidot pieteikumu @@ -1581,8 +1605,8 @@ issues.filter_sort.mostcomment=Visvairāk piebilžu issues.filter_sort.leastcomment=Vismazāk piebilžu issues.filter_sort.nearduedate=Tuvākais termiņš issues.filter_sort.farduedate=Tālākais termiņš -issues.filter_sort.moststars=Visvairāk atzīmētie -issues.filter_sort.feweststars=Vismazāk atzīmētie +issues.filter_sort.moststars=Visvairāk zvaigžņu +issues.filter_sort.feweststars=Vismazāk zvaigžņu issues.filter_sort.mostforks=Visvairāk atzarojumu issues.filter_sort.fewestforks=Vismazāk atzarojumu issues.keyword_search_unavailable=Meklēšana pēc atslēgvārda pašreiz nav pieejama. Lūgums sazināties ar vietnes administratoru. @@ -1594,7 +1618,7 @@ issues.action_milestone_no_select=Nav atskaites punkta issues.action_assignee=Atbildīgais issues.action_assignee_no_select=Nav atbildīgā issues.action_check=Atzīmēt/Notīrīt -issues.action_check_all=Atzīmēt/Notīrīt visus ierakstus +issues.action_check_all=Atzīmēt/Notīrīt visus vienumus issues.opened_by=%[3]s atvēra %[1]s pulls.merged_by=%[3]s iekļāva %[1]s pulls.merged_by_fake=%[2]s iekļāva %[1]s @@ -1722,7 +1746,7 @@ issues.del_time=Izdzēst šo laika žurnāla ierakstu issues.add_time_short=Pievienot laiku issues.add_time_cancel=Atcelt issues.add_time_history=` pievienoja patērēto laiku %s` -issues.del_time_history=`izdzēsa patērētais laiks %s` +issues.del_time_history=`izdzēsa patērēto laiku %s` issues.add_time_hours=Stundas issues.add_time_minutes=Minūtes issues.add_time_sum_to_small=Nav norādīts laiks. @@ -1805,7 +1829,7 @@ issues.review.show_resolved=Rādīt atrisināto issues.review.hide_resolved=Paslēpt atrisināto issues.review.resolve_conversation=Atrisināt sarunu issues.review.un_resolve_conversation=Atcelt sarunas atrisinājumu -issues.review.resolved_by=atzīmēja sarunu kā atrisinātu +issues.review.resolved_by=atzīmēja šo sarunu kā atrisinātu issues.assignee.error=Ne visi atbildīgie tika pievienoti, jo radās neparedzēta kļūda. issues.reference_issue.body=Saturs issues.content_history.deleted=izdzēsts @@ -2000,7 +2024,7 @@ signing.wont_sign.error=Atgadījās kļūda pārbaudot, vai iesūtījums var tik signing.wont_sign.nokey=Nav pieejamas atslēgas, ar ko parakstīt šo iesūtījumu. signing.wont_sign.never=Iesūtījumi nekad netiek parakstīti. signing.wont_sign.always=Iesūtījumi vienmēr tiek parakstīti. -signing.wont_sign.pubkey=Iesūtījums netiks parakstīts, jo kontam nav piesaistīta publiskā atslēga. +signing.wont_sign.pubkey=Iesūtījums netiks parakstīts, jo kontam nav piesaistīta publiska atslēga. signing.wont_sign.twofa=Jābūt iespējotai divpakāpju autentificēšanai, lai parakstītu iesūtījumus. signing.wont_sign.parentsigned=Iesūtījums netiks parakstīts, jo nav parakstīts cilmes iesūtījums. signing.wont_sign.basesigned=Apvienošana netiks parakstīta, jo pamata iesūtījums nav parakstīts. @@ -2081,7 +2105,7 @@ activity.unresolved_conv_desc=Šie nesen mainītie pieteikumi un izmaiņu piepra activity.unresolved_conv_label=Atvērts activity.title.releases_1=%d laidiens activity.title.releases_n=%d laidieni -activity.title.releases_published_by=%s publicēja %s +activity.title.releases_published_by=%s laida klājā %s activity.published_release_label=Laidiens activity.no_git_activity=Šajā laika periodā nav notikušas nekādas izmaiņas. activity.git_stats_exclude_merges=Neskaitot apvienošanas iesūtījumus, @@ -2444,7 +2468,7 @@ settings.protect_status_check_patterns_desc=Jāievada paraugi, lai norādītu, k settings.protect_check_status_contexts_desc=Pirms apvienošanas ir nepieciešama sekmīga stāvokļa pārbaužu izpilde. Kad iespējots, iesūtījumiem vispirms jābūt aizgādātiem citā zarā, tad pēc stāvokļa pārbaužu sekmīgas izpildes iekļautiem vai aizgādātiem tieši zarā, kas atbilst šai kārtulai. Ja nav atbilstošu kontekstu, pēdējam iesūtījumam jābūt sekmīgam neatkarīgi no konteksta. settings.protect_check_status_contexts_list=Stāvokļa pārbaudes, kas šajā glabātavā atrastas pēdējās nedēļas laikā settings.protect_status_check_matched=Atbilst -settings.protect_invalid_status_check_pattern=Kļūdains statusa pārbaudes šablons: "%s". +settings.protect_invalid_status_check_pattern=Nederīgs stāvokļa pārbaudes paraugs: "%s". settings.protect_no_valid_status_check_patterns=Nav derīgu stāvokļa pārbaužu paraugu. settings.protect_required_approvals=Nepieciešamie apstiprinājumi settings.protect_required_approvals_desc=Atļaut iekļaut izmaiņu pieprasījumu tikai ar pietiekamu daudzumu apstiprinošu izskatīšanu. @@ -2458,7 +2482,7 @@ settings.require_signed_commits=Pieprasīt parakstītus iesūtījumus settings.require_signed_commits_desc=Noraidīt aizgādāšanu uz šo zaru, ja iesūtījumi nav parakstīti vai apliecināmi. settings.protect_branch_name_pattern=Aizsargātā zara nosaukuma paraugs settings.protect_branch_name_pattern_desc=Aizsargāto zaru nosaukumu paraugi. Paraugu pierakstu skatīt dokumentācijā. Piemēri: main, release/** -settings.protect_patterns=Šabloni +settings.protect_patterns=Paraugi settings.protect_protected_file_patterns=Aizsargāto datņu paraugs (vairākus atdala ar semikolu ";") settings.protect_protected_file_patterns_desc=Aizsargātās datnes nav ļauts tiešā veidā mainīt, pat ja lietotājam šajā zarā ir tiesības pievienot, labot vai izdzēst datnes. Vairākus paraugus var atdalīt ar semikolu (";"). Paraugu pieraksts ir skatāms %[2]s dokumentācijā. Piemēri: .drone.yml, /docs/**/*.txt. settings.protect_unprotected_file_patterns=Neaizsargāto datņu paraugs (vairākus atdala ar semikolu ";") @@ -2517,7 +2541,7 @@ settings.unarchive.error=Glabātavas arhivēšanas atcelšanas laikā atgadījā settings.update_avatar_success=Glabātavas attēls tika atjaunināts. settings.lfs=LFS settings.lfs_filelist=Šajā glabātavā uzglabātās LFS datnes -settings.lfs_no_lfs_files=Šajā glabātavā nav uzglabātu LFS datņu +settings.lfs_no_lfs_files=Šajā glabātavā netiek glabātas LFS datnes settings.lfs_findcommits=Atrast iesūtījumus settings.lfs_lfs_file_no_commits=Šai LFS datnei netika atrasts neviens iesūtījums settings.lfs_noattribute=Šim ceļam noklusējuma zarā nav slēdzamības atribūta @@ -2533,8 +2557,8 @@ settings.lfs_lock_path=Slēdzamās datnes ceļš... settings.lfs_locks_no_locks=Nav slēdzeņu settings.lfs_lock_file_no_exist=Aizslēgtā datne nepastāv noklusējuma zarā settings.lfs_force_unlock=Uzspiest atslēgšanu -settings.lfs_pointers.found=Atrasta(s) %d binārā objekta norāde(s) - %d saistītas, %d nesaistītas (%d trūkstošas glabātuvē) -settings.lfs_pointers.sha=Binārā objekta jaucējkods +settings.lfs_pointers.found=Atrasta(s) %d binārā objekta norāde(s) - %d saistīta(s), %d nesaistīta(s) (%d trūkst krātuvē) +settings.lfs_pointers.sha=Binārā objekta jaucējvirkne settings.lfs_pointers.oid=OID settings.lfs_pointers.inRepo=Glabātavā settings.lfs_pointers.exists=Pastāv krātuvē @@ -2564,7 +2588,7 @@ diff.whitespace_ignore_all_whitespace=Neņemt vērā atstarpes, kad tiek salīdz diff.whitespace_ignore_amount_changes=Neņemt vērā atstarpju daudzuma izmaiņas diff.whitespace_ignore_at_eol=Neņemt vērā atstarpju izmaiņas rindu beigās diff.stats_desc=%d izmainītas datnes ar %d papildinājumiem un %d izdzēšanām -diff.stats_desc_file=%d izmaiņas: %d pievienotas un %d dzēstas +diff.stats_desc_file=%d izmaiņas: %d pievienošanas un %d izdzēšanas diff.bin=Binārs diff.bin_not_shown=Binārā datne netiek rādīta. diff.view_file=Apskatīt datni @@ -2615,7 +2639,7 @@ release.stable=Stabila release.compare=Salīdzināt release.edit=Labot release.ahead.commits=%d iesūtījumi -release.ahead.target=no %s kopš laidiena publicēšanas +release.ahead.target=%s kopš šī laidiena laišanas klajā tag.ahead.target=%s kopš šīs birkas release.source_code=Pirmkods release.new_subheader=Laidieni apkopo projekta versijas. @@ -2629,7 +2653,7 @@ release.title=Laidiena nosaukums release.title_empty=Nosaukums nevar būt tukšs. release.message=Aprakstīt šo laidienu release.prerelease_desc=Atzīmēt kā pirmsizlaidi -release.prerelease_helper=Atzīmēt, ka šo laidienu nav ieteicams lietot produkcijā. +release.prerelease_helper=Atzīmēt šo laidienu kā nepiemērotu izmantošanai produkcijā. release.cancel=Atcelt release.publish=Laist klajā laidienu release.save_draft=Saglabāt melnrakstu @@ -2640,10 +2664,10 @@ release.deletion=Izdzēst laidienu release.deletion_desc=Laidiena izdzēšana tikai noņem to no Forgejo. Tā neietekmēs Git birku, glabātavas saturu vai vēsturi. Turpināt? release.deletion_success=Laidiens tika izdzēsts. release.deletion_tag_desc=Šī birka tiks izdzēsta no glabātavas. Glabātavas saturs un vēsture paliks nemainīta. Turpināt? -release.deletion_tag_success=Tags tika izdzēsts. +release.deletion_tag_success=Birka tika izdzēsta. release.tag_name_already_exist=Laidiens ar šādu birkas nosaukumu jau pastāv. release.tag_name_invalid=Nederīgs birkas nosaukums. -release.tag_name_protected=Taga nosaukums ir aizsargāts. +release.tag_name_protected=Birkas nosaukums ir aizsargāts. release.tag_already_exist=Šāds birkas nosaukums jau pastāv. release.downloads=Lejupielādes release.download_count=Lejupielādes: %s @@ -2693,7 +2717,7 @@ tag.create_tag_operation=Izveidot birku tag.confirm_create_tag=Izveidot birku tag.create_tag_from=Izveidot jaunu birku no "%s" -tag.create_success=Tags "%s" tika izveidots. +tag.create_success=Birka "%s" tika izveidota. topic.manage_topics=Pārvaldīt tēmas topic.done=Gatavs @@ -2875,6 +2899,10 @@ editor.commit_email = Iesūtījuma e-pasta adrese commits.view_single_diff = Apskatīt šajā datnē veiktās izmaiņas šajā iesūtījumā pulls.editable = Labojams pulls.editable_explanation = Šis izmaiņu pieprasījums pieļauj labojumus no uzturētājiem. Tu vari tieši līdzdarboties tajā. +issues.reopen.blocked_by_user = Tu nevari atkārtoti atvērt šo pieteikumu, jo tā izveidotājs vai glabātavas īpašnieks ir liedzis Tevi. +pulls.comment.blocked_by_user = Tu šim izmaiņu pieprasījumam nevari pievienot piebildi, jo tā izveidotājs vai glabātavas īpašnieks ir liedzis Tevi. +issues.filter_no_results = Nav vienumu +issues.filter_no_results_placeholder = Jāmēģina pielāgot meklēšanas atlasītāji. [graphs] component_loading=Ielādē %s... @@ -2921,7 +2949,7 @@ settings.location=Atrašanās vieta settings.permission=Tiesības settings.repoadminchangeteam=Glabātavas pārvaldītājs var pievienot un noņemt komandu piekļuvi settings.visibility=Redzamība -settings.visibility.public=Publiska +settings.visibility.public=Atklāta settings.visibility.limited=Ierobežota (redzama tikai lietotājiem, kuri ir pieteikušies) settings.visibility.limited_shortname=Ierobežota settings.visibility.private=Privāta (redzama tikai apvienības dalībniekiem) @@ -2946,19 +2974,19 @@ members.membership_visibility=Dalībnieku redzamība: members.public=Redzams members.public_helper=Padarīt slēptu members.private=Slēpts -members.private_helper=Padarīt redzemu +members.private_helper=Padarīt redzamu members.member_role=Dalībnieka loma: members.owner=Īpašnieks members.member=Dalībnieks members.remove=Noņemt members.remove.detail=Noņemt %[1]s no %[2]s? -members.leave=Atstāt +members.leave=Pamest members.leave.detail=Vai tiešām pamest apvienību "%s"? -members.invite_desc=Pievienot jaunu dalībnieku pie %s: +members.invite_desc=Pievienot jaunu dalībnieku %s: members.invite_now=Uzaicināt tagad teams.join=Pievienoties -teams.leave=Atstāt +teams.leave=Pamest teams.leave.detail=Vai tiešām pamest komandu "%s"? teams.can_create_org_repo=Izveidot glabātavas teams.can_create_org_repo_helper=Dalībnieki apvienībā var izveidot jaunas glabātavas. Izveidotājs iegūs jaunās glabātavas pārvaldītāja piekļuvi. @@ -2966,11 +2994,11 @@ teams.none_access=Nav piekļuves teams.none_access_helper="Nav piekļuve" iespēja iedarbojas tikai privātās glabātavās. teams.general_access=Pielāgota piekļuve teams.general_access_helper=Komandas tiesības tiks noteiktas pēc tabulas zemāk. -teams.read_access=Skatīšanās +teams.read_access=Lasīt teams.read_access_helper=Komanda varēs skatīties un klonēt šīs organizācijas repozitorijus. -teams.write_access=Rakstīšanas +teams.write_access=Rakstīt teams.write_access_helper=Šī komanda varēs lasīt un nosūtīt izmaiņas uz tās repozitorijiem. -teams.admin_access=Pārvaldītaja piekļuve +teams.admin_access=Pārvaldītāja piekļuve teams.admin_access_helper=Dalībnieki var atgādāt un aizgādāt izmaiņas uz komandas glabātavām un pievienot tām līdzdalībniekus. teams.no_desc=Komandai nav apraksta teams.settings=Iestatījumi @@ -3027,7 +3055,7 @@ authentication=Autentificēšanas avoti emails=Lietotāju e-pasta adreses config=Konfigurācija notices=Sistēmas paziņojumi -monitor=Uzraudzība +monitor=Pārraudzīšana first_page=Pirmā last_page=Pēdējā total=Kopā: %d @@ -3178,8 +3206,8 @@ users.details=Lietotāja informācija emails.email_manage_panel=Pārvaldīt lietotāju e-pasta adreses emails.primary=Galvenā emails.activated=Aktivēta -emails.filter_sort.email=E-pasts -emails.filter_sort.email_reverse=E-pasta adrese (pretēji alfabētiski) +emails.filter_sort.email=E-pasta adrese +emails.filter_sort.email_reverse=E-pasta adrese (apvērsti) emails.filter_sort.name=Lietotājvārds emails.filter_sort.name_reverse=Lietotāja vārds (apvērsti) emails.updated=E-pasta adrese atjaunināta @@ -3195,11 +3223,11 @@ orgs.members=Dalībnieki orgs.new_orga=Jauna apvienība repos.repo_manage_panel=Pārvaldīt glabātavas -repos.unadopted=Nepieemtās glabātavas +repos.unadopted=Nepieņemtās glabātavas repos.unadopted.no_more=Nav atrasta neviena nepieņemta glabātava. repos.owner=Īpašnieks repos.name=Nosaukums -repos.private=Privāts +repos.private=Privāta repos.watches=Vērošana repos.stars=Zvaigznes repos.forks=Atdalītie @@ -3219,7 +3247,7 @@ packages.version=Versija packages.type=Veids packages.repository=Glabātava packages.size=Izmērs -packages.published=Publicēts +packages.published=Laista klajā defaulthooks=Noklusējuma tīmekļa aizķeres defaulthooks.desc=Tīmekļa aizķeres automātiski nosūta HTTP POST pieprasījumus serverim, kad iestājas noteikti Forgejo notikumi. Šeit esošās tīmekļa aizķeres ir noklusējuma, un tās tiks ievietotas visās jaunajās glabātavās. Vairāk ir lasāms norādēs par tīmekļa aizķerēm. @@ -3306,7 +3334,7 @@ auths.oauth2_group_claim_name=Prasības nosaukums, kas šim avotam nodrošina gr auths.oauth2_admin_group=Kopas prasības vērtība pārvaldītājiem. (Izvēles - nepieciešams augstāk esošais prasības nosaukums) auths.oauth2_restricted_group=Grupas prasības vērtība ierobežotajiem lietotājiem. (Izvēles - nepieciešams augstāk esošais prasības nosaukums) auths.oauth2_map_group_to_team=Sasaistīt pieprasītās kopas ar apvienības komandām. (Izvēles - nepieciešams augstāk esošais prasības nosaukums) -auths.oauth2_map_group_to_team_removal=Noņemt lietotājus no sinhronizētajām komandām, ja lietotājs nav piesaistīts attiecīgajai grupai. +auths.oauth2_map_group_to_team_removal=Noņemt lietotājus no sinhronizētajām komandām, ja lietotājs nav attiecīgajā grupai. auths.enable_auto_register=Iespējot automātisko reģistrāciju auths.sspi_auto_create_users=Automātiski izveidot lietotājus auths.sspi_auto_create_users_helper=Ļauj SSPI autentificēšanās veidam automātiski izveidot jaunus kontus lietotājiem, kas piesakās pirmo reizi @@ -3334,20 +3362,20 @@ auths.tip.twitter=Jādodas uz %s, jāizveido lietotne un jānodrošina, ka iesp auths.tip.discord=Jāizveido jauna lietotne %s auths.tip.gitea=Pievienot jaunu OAuth2 lietotni. Norādes ir atrodamas %s auths.tip.yandex=%s jāizveido jauna lietotne. Sadaļā "Yandex.Passport API" jāatlasa šīs atļaujas: "Access to email address", "Access to user avatar" un "Access to username, first name and surname, gender" -auths.tip.mastodon=Jāievada pielāgota Mastodon servera URL, ar kuru vēlies autentificēties (vai jāizmanto noklusējuma) +auths.tip.mastodon=Jāievada pielāgota Mastodon servera URL, ar kuru ir vēlēšanās autentificēties (vai jāizmanto noklusējuma) auths.edit=Labot autentificēšanas avotu -auths.activated=Autentificēšanas avots ir atkivēts -auths.new_success=Jauna autentifikācija "%s" tika pievienota. +auths.activated=Šis autentificēšanas avots ir atkivēts +auths.new_success=Autentificēšanās "%s" tika pievienota. auths.update_success=Autentificēšanās avots tika atjaunināts. auths.update=Atjaunināt autentificēšanās avotu auths.delete=Izdzēst autentificēšanas avotu auths.delete_auth_title=Izdzēst autentificēšanas avotu -auths.delete_auth_desc=Izdzēšot autentifikācijas avotu, tā lietotājiem nebūs iespējams pieteikties. Vai turpināt? +auths.delete_auth_desc=Autentificēšanās avota izdzēšana liedz lietotājiem to izmantot, lai pieteiktos. Turpināt? auths.still_in_used=Šo autentificēšanās avotu joprojām izmanto viens vai vairāki lietotāji, tos nepieciešams izdzēst vai pārvietot uz citu autentificēšanās avotu. auths.deletion_success=Autentificēšanās avots tika izdzēsts. auths.login_source_exist=Jau pastāv autentificēšanās avots "%s". auths.login_source_of_type_exist=Jau pastāv šāda veida autentificēšanās avots. -auths.unable_to_initialize_openid=Nevarēja inicializēt OpenID Connect sliedzēju: %s +auths.unable_to_initialize_openid=Nevarēja sāknēt OpenID Connect sniedzēju: %s auths.invalid_openIdConnectAutoDiscoveryURL=Nederīgs automātiskās atklāšanas URL (tam jābūt derīgam URL, kas sākas ar http:// vai https://) config.server_config=Servera konfigurācija @@ -3500,7 +3528,7 @@ monitor.download_diagnosis_report=Lejupielādēt diagnostikas atskaiti monitor.desc=Apraksts monitor.start=Sākuma laiks monitor.execute_time=Izpildes laiks -monitor.last_execution_result=Rezultāts +monitor.last_execution_result=Iznākums monitor.process.cancel=Atcelt procesu monitor.process.cancel_desc=Procesa atcelšana var radīt datu zaudējumus monitor.process.cancel_notices=Atcelt: %s? @@ -3520,7 +3548,7 @@ monitor.queue.settings.title=Pūla iestatījumi monitor.queue.settings.desc=Pūli dinamiski palielinās atkarībā no to strādņu rindu aizturēšanas. monitor.queue.settings.maxnumberworkers=Maksimālais strādņu skaits monitor.queue.settings.maxnumberworkers.placeholder=Pašalaik %[1]d -monitor.queue.settings.maxnumberworkers.error=Maksimālajam strādņu skaitam ir jābūt skaitlim +monitor.queue.settings.maxnumberworkers.error=Lielākajam pieļaujamajam strādņu skaitam ir jābūt skaitlim monitor.queue.settings.submit=Atjaunināt iestatījumus monitor.queue.settings.changed=Iestatījumi atjaunināti monitor.queue.settings.remove_all_items=Noņemt visus @@ -3539,7 +3567,7 @@ notices.type_1=Glabātava notices.type_2=Uzdevums notices.desc=Apraksts notices.op=Op. -notices.delete_success=Sistēmas paziņojumi ir dzēsti. +notices.delete_success=Sistēmas paziņojumi tika dzēsti. self_check.no_problem_found=Vēl nav atrasts neviens sarežģījums. config_summary = Kopsavilkums @@ -3556,7 +3584,7 @@ self_check.database_collation_mismatch = Sagaidīt, ka datubāzē tiek izmantota self_check.database_fix_mysql = MySQL/MariaDB lietotāji var izmantot komandu "forgejo doctor convert", lai novērstu salīdzināšanas sarežģījumus, vai arī tos var pašrocīgi novērst ar "ALTER ... COLLATE ..." vaicājumiem. config.app_slogan = Servera sauklis config.allow_dots_in_usernames = Ļaut lietotājiem izmantot punktus savā lietotājvārdā. Neietekmē esošos kontus. -users.restricted.description = Ļaut mijiedarbību tikai ar glabātavām un apvienībām, kurās šis lietotājs ir pievienots kā līdzdalībnieks. Tas neļauj piekļūt šī servera publiskajām glabātavām. +users.restricted.description = Ļaut mijiedarbību tikai ar glabātavām un apvienībām, kurās šis lietotājs ir pievienots kā līdzdalībnieks. Tas neļauj piekļūt šī servera atklātajām glabātavām. dashboard.sync_tag.started = Uzsākta birku sinhronizēšana users.organization_creation.description = Ļaut jaunu apvienību izveidošanu. users.block.description = Liegt šī lietotāja mijiedarbību ar šo serveri caur tā kontu un neļaut pieteikšanos. @@ -3603,7 +3631,7 @@ review_dismissed=`atmeta izskatīšanu no %[4]s %[3]s#%[2 review_dismissed_reason=Iemesls: create_branch=izveidoja zaru %[3]s glabātavā %[4]s starred_repo=pievienoja izlasē %[2]s -watched_repo=sāka sekot %[2]s +watched_repo=sāka vērot %[2]s [tool] now=tagad @@ -3635,11 +3663,11 @@ remove_file=Noņemt datni notifications=Paziņojumi unread=Neizlasītie read=Izlasītie -no_unread=Nav nelasītu paziņojumu. +no_unread=Nav neizlasītu paziņojumu. no_read=Nav izlasītu paziņojumu. pin=Piespraust paziņojumu mark_as_read=Atzīmēt kā izlasītu -mark_as_unread=Atzīmēt kā nelasītu +mark_as_unread=Atzīmēt kā neizlasītu mark_all_as_read=Atzīmēt visus kā izlasītus subscriptions=Abonementi watching=Skatās @@ -3671,9 +3699,9 @@ registry.documentation=Vairāk informācijas par %s reģistru ir %[3]s %[1]s +filter.container.tagged=Ar birku +filter.container.untagged=Bez birkas +published_by=Laida klajā %[3]s %[1]s published_by_in=%[3]s laida klajā %[1]s %[5]s installation=Uzstādīšana about=Par šo pakotni @@ -3782,7 +3810,7 @@ settings.delete.error=Neizdevās izdzēst pakotni. owner.settings.cargo.title=Cargo reģistra inkdess owner.settings.cargo.initialize=Sāknēt indeksu owner.settings.cargo.initialize.description=Ir nepieciešams īpaša indeksa Git glabātava, lai izmantotu Cargo reģistru. Šīs iespējas izmantošana (atkārtoti) izveidos glabātavu un automātiski to iestatīs. -owner.settings.cargo.initialize.error=Neizdevās inicializēt Cargo indeksu: %v +owner.settings.cargo.initialize.error=Neizdevās sāknēt Cargo indeksu: %v owner.settings.cargo.initialize.success=Cargo indekss tika sekmīgi izveidots. owner.settings.cargo.rebuild=Pārbūvēt indeksu owner.settings.cargo.rebuild.description=Pārbūvēšana var būt noderīga, ja indekss nav sinhronizēts ar saglabātajām Cargo pakotnēm. @@ -3793,12 +3821,12 @@ owner.settings.cleanuprules.add=Pievienot notīrīšanas kārtulu owner.settings.cleanuprules.edit=Labot notīrīšanas kārtulu owner.settings.cleanuprules.none=Vēl nav pieejama neviena tīrīšanas kārtula. owner.settings.cleanuprules.preview=Attīrīšanas kārtulas priekšskatījums -owner.settings.cleanuprules.preview.overview=Ir ieplānota %d paku dzēšana. -owner.settings.cleanuprules.preview.none=Notīrīšanas kārtulai neatbilst neviena pakotne. +owner.settings.cleanuprules.preview.overview=Ir paredzēta %d pakotņu noņemšana. +owner.settings.cleanuprules.preview.none=Attīrīšanas kārtulai neatbilst neviena pakotne. owner.settings.cleanuprules.enabled=Iespējots -owner.settings.cleanuprules.pattern_full_match=Piešķirt šablonu visam pakotnes nosaukumam +owner.settings.cleanuprules.pattern_full_match=Pielietot paraugu visam pakotnes nosaukumam owner.settings.cleanuprules.keep.title=Versijas, kas atbilst šīm kārtulām, tiks paturētas, pat ja tās atbildīs zemāk esošajai noņemšanas kārtulai. -owner.settings.cleanuprules.keep.count=Saglabāt jaunāko versiju +owner.settings.cleanuprules.keep.count=Paturēt visjaunāko owner.settings.cleanuprules.keep.count.1=1 versija katrai pakotnei owner.settings.cleanuprules.keep.count.n=%d versijas katrai pakotnei owner.settings.cleanuprules.keep.pattern=Paturēt versijas, kas atbilst @@ -3835,24 +3863,24 @@ search_in_external_registry = Meklēt %s alt.registry = Šī reģistra uzstādīšana komandrindā: alt.registry.install = Lai uzstādītu pakotni, jāizpilda šī komanda: alt.install = Uzstādīt pakotni -alt.setup = Pievienot glabātavu savienoto glabātavu sarakstā ('_arch_' vietā jāizvēlas nepieciešamā arhitektūra): +alt.setup = Pievienot glabātavu savienoto glabātavu sarakstā ("_arch_" vietā jāizvēlas nepieciešamā arhitektūra): alt.repository = Informācija par glabātavu alt.repository.architectures = Arhitektūras alt.repository.multiple_groups = Šī pakotne ir pieejama vairākās kopās. [secrets] secrets=Noslēpumi -description=Noslēpumi tiks padoti atsevišķām darbībām un citādi nevar tikt nolasīti. +description=Noslēpumi tiks padoti noteiktām darbībām, un citādāk tos nevar nolasīt. none=Pagaidām nav neviena noslēpuma. creation=Pievienot noslēpumu creation.name_placeholder=reģistrnejutīgs, tikai burti, cipari un apakšsvītras, nevar sākties ar GITEA_ vai GITHUB_ creation.value_placeholder=Jāievada jebkāds saturs. Atstarpes sākumā un beigās tiks izlaistas. creation.success=Noslēpums "%s" tika pievienots. creation.failed=Neizdevās pievienot noslēpumu. -deletion=Dzēst noslēpumu -deletion.description=Noslēpuma dzēšana ir neatgriezeniska. Vai turpināt? -deletion.success=Noslēpums tika izdzēsts. -deletion.failed=Neizdevās dzēst noslēpumu. +deletion=Noņemt noslēpumu +deletion.description=Noslēpuma izdzēšana ir neatgriezeniska un nav atsaucama. Turpināt? +deletion.success=Noslēpums tika noņemts. +deletion.failed=Neizdevās noņemt noslēpumu. management=Pārvaldīt noslēpumus [actions] @@ -3873,7 +3901,7 @@ runners=Izpildītāji runners.runner_manage_panel=Pārvaldīt izpildītājus runners.new=Izveidot jaunu izpildītāju runners.new_notice=Kā uzstādīt izpildītāju -runners.status=Statuss +runners.status=Stāvoklis runners.id=ID runners.name=Nosaukums runners.owner_type=Veids @@ -3884,7 +3912,7 @@ runners.runner_title=Izpildītājs runners.task_list=Pēdējās darbības, kas izpildītas runners.task_list.no_tasks=Vēl nav uzdevumu. runners.task_list.run=Izpildījums -runners.task_list.status=Statuss +runners.task_list.status=Stāvoklis runners.task_list.repository=Glabātava runners.task_list.commit=Iesūtījums runners.task_list.done_at=Beigu laiks @@ -3896,11 +3924,11 @@ runners.delete_runner=Dzēst izpildītāju runners.delete_runner_success=Izpildītājs sekmīgi izdzēsts runners.delete_runner_failed=Neizdevās izdzēst izpildītāju runners.delete_runner_header=Apstiprināt izpildītāja izdzēšanu -runners.delete_runner_notice=Ja šis izpildītājs veic kādus uzdevumus, tad tie tiks apturēti un atzīmēti kā neizdevušies. Tas var sabojāt būvēšanas darbaplūsmas. +runners.delete_runner_notice=Ja šis izpildītājs veic kādus uzdevumus, tad tie tiks apturēti un atzīmēti kā neizdevušies. Tas var sabojāt būvēšanas darbplūsmas. runners.none=Nav pieejami izpildītāji runners.status.unspecified=Nezināms runners.status.idle=Dīkstāvē -runners.status.active=Aktīvs +runners.status.active=Darbojas runners.status.offline=Bezsaistē runners.version=Versija runners.reset_registration_token=Atiestatīt reģistrācijas pilnvaru @@ -3913,7 +3941,7 @@ runs.pushed_by=aizgādāja runs.invalid_workflow_helper=Darbplūsmas konfigurācijas datne ir nederīga. Lūgums pārbaudīt konfigurācijas datni: %s runs.no_matching_online_runner_helper=Nav tiešsaistē esošu izpildītāju, kas atbilstu iezīmei: %s runs.actor=Izraisītājs -runs.status=Statuss +runs.status=Stāvoklis runs.actors_no_select=Visi izraisītāji runs.status_no_select=Visi stāvokļi runs.no_results=Netika atrasts nekas atbilstošs. diff --git a/options/locale/locale_nds.ini b/options/locale/locale_nds.ini index ee374a2549..9715d76e86 100644 --- a/options/locale/locale_nds.ini +++ b/options/locale/locale_nds.ini @@ -1473,7 +1473,7 @@ issues.label.filter_sort.reverse_alphabetically = Umdreiht na de Alphabeet issues.label.filter_sort.by_size = Lüttste Grött issues.num_participants_one = %d Mitmaker issues.num_participants_few = %d Mitmakers -issues.ref_pull_from = `hett deeses Haalvörslag %[4]s %[2]s benöömt` +issues.ref_pull_from = `hett deesen Haalvörslag %[4]s %[2]s benöömt` issues.label_title = Naam issues.label_archived_filter = Archiveert Vermarkens wiesen issues.archived_label_description = (Archiveert) %s @@ -1581,7 +1581,7 @@ issues.start_tracking_history = `hett %s to warken begunnen` issues.lock.notice_2 = - Du un anner Mitarbeiders mit Togriep to deesem Repositorium köönt wiederhen Kommentaren schrieven, wat elkeenwell sücht. issues.due_date_modified = hett dat Anstahns-Datum vun %[2]s to %[1]s %[3]s ännert issues.dependency.issue_remove_text = Dat word de Ofhangen vun deesem Gefall wegdoon. Wiedermaken? -issues.review.approve = hett deese Ännerns %s tostimmt +issues.review.approve = hett %s deesen Ännerns tostimmt issues.review.dismissed = hett %[2]s dat Nakieken vun %[1]s ofseggt issues.lock.title = Snack up deesem Gefall tosluten. issues.unlock.title = Snack up deesem Gefall upsluten. @@ -1692,12 +1692,12 @@ pulls.waiting_count_1 = %d Nakieken staht ut issues.content_history.deleted = lösket issues.content_history.created = maakt issues.content_history.delete_from_history_confirm = Ut Histoorje lösken? -issues.blocked_by_user = Du kannst up deesem Repositorium keenen Gefall opmaken, denn de Repositoriums-Eegner hett di blockeert. +issues.blocked_by_user = Du kannst in deesem Repositorium keene Gefallens opmaken, denn de Repositoriums-Eegner hett di blockeert. pulls.merged_title_desc_few = hett %[1]d Kommitterens vun %[2]s na %[3]s %[4]s tosamenföhrt pulls.reject_count_1 = %d Bidde um Ännerns pulls.blocked_by_user = Du kannst in deesem Repositorium keenen Haalvörslag opmaken, denn de Repositoriums-Eegner hett di blockeert. pulls.no_merge_access = Du hest nich dat Recht, deesen Haalvörslag tosamentoföhren. -issues.comment.blocked_by_user = Du kannst up deesem Gefall keenen Kommentaar schrieven, denn de Repositoriums-Eegner of de Autor vun de Gefall hett di blockeert. +issues.comment.blocked_by_user = Du kannst up deesem Gefall nich kommenteren, denn de Repositoriums-Eegner of de Autor vun de Gefall hett di blockeert. pulls.switch_comparison_type = Verglieks-Aard ännern pulls.showing_only_single_commit = Blots Ännerns vun Kommitteren %[1]s wiesen pulls.blocked_by_changed_protected_files_n = Deeser Haalvörslag is blockeert, denn dat ännert beschütt Dateien: @@ -2600,6 +2600,10 @@ archive.pull.noreview = Deeses Repositorium is archiveert. Du kannst keene Haalv commits.view_single_diff = Ännerns an deeser Datei in deesem Kommitteren wiesen pulls.editable = Bewarkbaar pulls.editable_explanation = Deeser Haalvörslag verlöövt Bewarkens vun Liddmaten. Du kannst stracks daarto bidragen. +issues.reopen.blocked_by_user = Du kannst deeses Gefall nich weer opmaken, denn de Repositoriums-Eegner of de Autor vun de Gefall hett di blockeert. +pulls.comment.blocked_by_user = Du kannst up deesem Haalvörslag nich kommenteren, denn de Repositoriums-Eegner of de Autor vun de Haalvörslag hett di blockeert. +issues.filter_no_results = Keene Resultaten +issues.filter_no_results_placeholder = Versöök, diene Söök-Filters antopassen. [repo.permissions] code.read = Lesen: De Quelltext vun deesem Repositorium ankieken un klonen. diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 415ca8948e..8b5db6ea50 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -4,14 +4,14 @@ dashboard=Overzicht explore=Verkennen help=Help logo=Logo -sign_in=Inloggen +sign_in=Aanmelden sign_in_or=of sign_out=Uitloggen sign_up=Registreren link_account=Account Koppelen register=Registreren version=Versie -powered_by=Aangedreven door %s +powered_by=Mogelijk gemaakt door %s page=Pagina template=Sjabloon language=Taal @@ -28,24 +28,24 @@ username=Gebruikersnaam email=E-mailadres password=Wachtwoord access_token=Toegangstoken -re_type=Verifieer wachtwoord +re_type=Bevestig wachtwoord captcha=CAPTCHA twofa=Twee-factor authenticatie twofa_scratch=Twee-factor krascode -passcode=PIN +passcode=Code webauthn_insert_key=Voer uw beveiligingssleutel in -webauthn_sign_in=Druk op de knop van uw beveiligingssleutel. Als uw beveiligingssleutel geen knop heeft, voeg deze dan opnieuw in. +webauthn_sign_in=Druk op de knop van uw beveiligingssleutel. Als uw beveiligingssleutel geen knop heeft, voer deze dan opnieuw in. webauthn_press_button=Druk alstublieft op de knop van uw beveiligingssleutel… webauthn_use_twofa=Gebruik een twee-factor code van uw telefoon webauthn_error=Kon uw beveiligingssleutel niet lezen. webauthn_unsupported_browser=Uw browser ondersteunt momenteel geen WebAuthn. webauthn_error_unknown=Er is een onbekende fout opgetreden. Probeer het opnieuw. -webauthn_error_insecure=WebAuthn ondersteunt alleen beveiligde verbindingen. Om te testen via HTTP, kan je de oorsprong "localhost" of "127.0.0.1" gebruiken +webauthn_error_insecure=WebAuthn ondersteunt alleen beveiligde verbindingen. Om te testen via HTTP, kan je als systeemnaam "localhost" of "127.0.0.1" gebruiken webauthn_error_unable_to_process=De server kon uw verzoek niet verwerken. -webauthn_error_duplicated=De beveiligingssleutel is niet toegestaan voor dit verzoek. Zorg er alstublieft voor dat de sleutel niet al geregistreerd is. +webauthn_error_duplicated=De beveiligingssleutel is voor dit verzoek niet toegestaan. Controleer alstublieft of de sleutel niet al is geregistreerd. webauthn_error_empty=U moet een naam voor deze sleutel instellen. -webauthn_error_timeout=Time-out bereikt voordat uw sleutel kon worden gelezen. Laad deze pagina opnieuw en probeer het opnieuw. +webauthn_error_timeout=Time-out bereikt voordat uw sleutel kon worden gelezen. Herlaad deze pagina en probeer het opnieuw. webauthn_reload=Vernieuwen repository=Repository @@ -56,9 +56,9 @@ new_migrate=Nieuwe migratie new_mirror=Nieuwe mirror new_fork=Nieuwe repository fork new_org=Nieuwe organisatie -new_project=Nieuwe project +new_project=Nieuw project manage_org=Beheer organisaties -admin_panel=Website administratie +admin_panel=Site beheer account_settings=Accountinstellingen settings=Instellingen your_profile=Profiel @@ -66,12 +66,12 @@ your_starred=Favoriet your_settings=Instellingen all=Alles -sources=Bronnen +sources=Broncode mirrors=Mirrors -collaborative=Samenwerkend +collaborative=Samenwerkende forks=Forks -activities=Activiteiten +activities=Activiteit pull_requests=Pull requests issues=Issues milestones=Mijlpalen @@ -83,7 +83,7 @@ add=Toevoegen add_all=Alles toevoegen remove=Verwijder remove_all=Alles verwijderen -edit=Bewerk +edit=Wijzig enabled=Ingeschakeld disabled=Uitgeschakeld @@ -115,8 +115,8 @@ concept_user_organization=Organisatie name=Naam -sign_in_with_provider = Log in met %s -tracked_time_summary = Overzicht van geregistreerde tijd op basis van filters van probleemlijst +sign_in_with_provider = Aanmelden met %s +tracked_time_summary = Overzicht van geregistreerde tijd op basis van filters van issuelijst enable_javascript = Deze website vereist JavaScript. retry = Probeer opnieuw rerun_all = Alle taken opnieuw uitvoeren @@ -140,9 +140,9 @@ confirm_delete_selected = Bevestigen om alle geselecteerde items te verwijderen? copy_type_unsupported = Dit bestandstype kan niet worden gekopieerd pin = Vastpinnen unpin = Ontpinnen -remove_label_str = Verwijder punt "%s" +remove_label_str = Verwijder item "%s" confirm_delete_artifact = Weet u zeker dat u het artefact "%s" wilt verwijderen? -toggle_menu = Menu schakelen +toggle_menu = Menu aan/uit filter.clear = Filter wissen filter.is_archived = Gearchiveerd filter.is_fork = Forks @@ -159,7 +159,7 @@ more_items = Meer items invalid_data = Ongeldige data: %v copy_generic = Kopieer naar klembord test = Test -error413 = U heeft al uw quotum opgebruikt. +error413 = U heeft uw hele quotum gebruikt. new_migrate.title = Nieuwe migratie new_org.title = Nieuwe organisatie new_repo.link = Nieuwe repository @@ -206,6 +206,10 @@ table_modal.placeholder.header = Kop table_modal.placeholder.content = Inhoud table_modal.label.rows = Rijen table_modal.label.columns = Kolommen +link_modal.header = Link toevoegen +link_modal.url = Url +link_modal.description = Beschrijving +link_modal.paste_reminder = Tip: Als u een URL op uw klembord heeft, kun u deze direct in de editor plakken om een koppeling te maken. [filter] string.asc = A - Z @@ -1064,6 +1068,26 @@ change_username_redirect_prompt.with_cooldown.few = De oude gebruikersnaam zal v change_username_redirect_prompt.with_cooldown.one = De oude gebruikersnaam zal voor iedereen beschikbaar zijn na een afkoelperiode van %[1]d dag. U kunt de oude gebruikersnaam nog steeds opeisen tijdens de afkoelperiode. keep_pronouns_private = Toon voornaamwoorden alleen aan geauthenticeerde gebruikers keep_pronouns_private.description = Dit verbergt uw voornaamwoorden voor bezoekers die niet zijn ingelogd. +quota.rule.exceeded.helper = De totale grootte van objecten voor deze regel heeft de quota overschreden. +quota.sizes.repos.private = Privé repositories +storage_overview = Opslagoverzicht +quota = Quotum +quota.applies_to_user = De volgende quotaregels zijn van toepassing op uw account +quota.applies_to_org = De volgende quotaregels zijn van toepassing op deze organisatie +quota.rule.exceeded = Overschreden +quota.rule.no_limit = Onbeperkt +quota.sizes.all = Alle +quota.sizes.repos.all = Repositories +quota.sizes.repos.public = Openbare repositories +quota.sizes.git.all = Git inhoud +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.all = Bezittingen +quota.sizes.assets.attachments.all = Bijlagen +quota.sizes.assets.attachments.issues = Issue bijlagen +quota.sizes.assets.attachments.releases = Release bijlagen +quota.sizes.assets.artifacts = Artefacten +quota.sizes.assets.packages.all = Pakketten +quota.sizes.wiki = Wiki [repo] owner=Eigenaar @@ -1257,7 +1281,7 @@ tags=Labels issues=Issues pulls=Pull requests project_board=Projecten -packages=Paketten +packages=Pakketten labels=Labels org_labels_desc=Organisatielabel dat gebruikt kan worden met alle repositories onder deze organisatie org_labels_desc_manage=beheren @@ -2397,8 +2421,8 @@ find_file.no_matching = Geen overeenkomstige bestanden gevonden error.csv.too_large = Kan dit bestand niet renderen omdat het te groot is. error.csv.unexpected = Kan dit bestand niet renderen omdat het een onverwacht karakter bevat in regel %d en kolom %d. error.csv.invalid_field_count = Kan dit bestand niet renderen omdat het een verkeerd aantal velden heeft in regel %d. -issues.comment.blocked_by_user = U kunt geen reactie op deze issue plaatsen omdat u geblokkeerd bent door de eigenaar van de repository of door de persoon die de issue heeft gepost. -issues.blocked_by_user = U kunt geen issue op deze repository maken omdat u geblokkeerd bent door de eigenaar van de repository. +issues.comment.blocked_by_user = U kunt niet reageren op deze issue omdat u geblokkeerd bent door de eigenaar van de repository of de poster van de issue. +issues.blocked_by_user = U kunt geen issues aanmaken in deze repository omdat u geblokkeerd bent door de eigenaar van deze repository. issues.label_archived_filter = Gearchiveerde labels bekijken issues.label_archive_tooltip = Gearchiveerde labels zijn standaard uitgezonderd van de suggesties als men op een label zoekt. issues.max_pinned = U kunt geen issues meer vastpinnen @@ -2874,6 +2898,10 @@ archive.pull.noreview = Deze repository is gearchiveerd. U kunt geen pull reques commits.view_single_diff = Bekijk de veranderingen aan dit bestand die in deze commit zijn geïntroduceerd pulls.editable_explanation = Deze pull request staat bewerkingen toe van beheerders. Je kunt er direct aan bijdragen. pulls.editable = Bewerkbaar +issues.reopen.blocked_by_user = U kunt deze issue niet heropenen omdat u geblokkeerd bent door de eigenaar van de repository of de poster van de issue. +pulls.comment.blocked_by_user = U kunt niet reageren op deze pull request omdat u geblokkeerd bent door de eigenaar van de repository of de poster van de issue. +issues.filter_no_results_placeholder = Probeer uw zoekfilters aan te passen. +issues.filter_no_results = Geen resultaten @@ -2951,7 +2979,7 @@ members.invite_desc=Voeg nieuw lid toe aan %s: members.invite_now=Nu uitnodigen teams.join=Lid worden -teams.leave=Vertlaat +teams.leave=Verlaat teams.can_create_org_repo=Maak repositories teams.can_create_org_repo_helper=Leden kunnen nieuwe repositories aanmaken in de organisatie. De maker krijgt beheerder toegang tot de nieuwe repository. teams.read_access=Gelezen @@ -3840,7 +3868,7 @@ alt.repository.architectures = Architecturen alt.repository.multiple_groups = Dit pakket is beschikbaar in meerdere groepen. alt.registry = Stel dit register in vanaf de opdrachtregel: alt.install = Pakket installeren -alt.setup = Voeg een repository toe aan de lijst met gekoppelde repositories (kies de benodigde architectuur in plaats van '_arch_'): +alt.setup = Voeg een repository toe aan de lijst met gekoppelde repositories (kies de benodigde architectuur in plaats van "_arch_"): [secrets] secrets = Geheimen diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 6ba696573a..7098481fee 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -55,7 +55,7 @@ organization=Organização mirror=Espelhamento new_repo=Novo repositório new_migrate=Nova migração -new_mirror=Novo espelhamento +new_mirror=Novo espelho new_fork=Novo fork do repositório new_org=Nova organização new_project=Novo projeto @@ -109,7 +109,7 @@ preview=Pré-visualização loading=Carregando… error=Erro -error404=A página que você está tentando acessar não existe ou você não está autorizado a visualizá-la. +error404=A página que você está tentando acessar não existe, foi removida ou você não tem autorização para visualizá-la. never=Nunca unknown=Desconhecido @@ -206,6 +206,10 @@ table_modal.placeholder.header = Cabeçalho table_modal.placeholder.content = Conteúdo table_modal.label.rows = Linhas table_modal.label.columns = Colunas +link_modal.header = Adicionar um link +link_modal.url = URL +link_modal.description = Descrição +link_modal.paste_reminder = Dica: Com uma URL na sua área de transferência, você pode colar diretamente no editor para criar um link. [filter] string.asc=A - Z @@ -723,8 +727,8 @@ following.title.one = seguindo followers.title.one = seguidor followers.title.few = seguidores public_activity.visibility_hint.self_private = Sua atividade está visível apenas para você e para os administradores da instância. Configurar. -public_activity.visibility_hint.self_public = Sua atividade está visível para todos, exceto o engajamento em espaços privados. Configurar. -public_activity.visibility_hint.admin_public = Sua atividade está visível para todos, mas como um administrador você também pode ver o engajamento em espaços privados. +public_activity.visibility_hint.self_public = Sua atividade está visível para todos, exceto interações em espaços privados. Configurar. +public_activity.visibility_hint.admin_public = Sua atividade está visível para todos, mas como um administrador você também pode ver interações em espaços privados. public_activity.visibility_hint.admin_private = Essa atividade está visível para você porque você é um administrador, mas o usuário dejesa que ela seja mantida em privado. public_activity.visibility_hint.self_private_profile = Sua atividade só é visível para você e para os administradores do servidor porque seu perfil é privado. Configurar. @@ -993,7 +997,7 @@ scan_this_image=Escaneie esta imagem com o seu aplicativo de autenticação: or_enter_secret=Ou digite esse código: %s then_enter_passcode=E insira a senha mostrada no aplicativo: passcode_invalid=Esse código de acesso é inválido. Tente novamente. -twofa_enrolled=Sua conta foi inscrita na autenticação de dois fatores. Armazene seu token de backup (%s) em um local seguro, pois ele é exibido apenas uma vez! +twofa_enrolled=Sua conta foi inscrita na autenticação de dois fatores. Armazene seu token de recuperação de uso único (%s) em um local seguro, pois ele não será exibido novamente. twofa_failed_get_secret=Falha ao obter o segredo. webauthn_desc=Chaves de segurança são dispositivos de hardware que contém chaves de criptografia. Elas podem ser usadas para autenticação de dois fatores. A chave de segurança deve suportar o padrão WebAuthnn Authenticator. @@ -1061,6 +1065,28 @@ user_block_yourself = Você não pode se bloquear. pronouns_custom_label = Pronomes personalizados change_username_redirect_prompt.with_cooldown.one = O nome de usuário antigo ficará disponível para qualquer pessoa após um período de espera de %[1]d dia, você ainda pode recuperar o nome de usuário antigo durante este período de espera. change_username_redirect_prompt.with_cooldown.few = O nome de usuário antigo ficará disponível para qualquer pessoa após um período de espera de %[1]d dias, você ainda pode recuperar o nome de usuário antigo durante este período de espera. +quota.applies_to_user = As seguintes regras de cota se aplicam à sua conta +quota.rule.exceeded.helper = O tamanho total de objetos para esta regra excedeu a cota. +keep_pronouns_private = Mostrar pronomes apenas para usuários autenticados +keep_pronouns_private.description = Isto irá esconder seus pronomes de visitantes que não fizeram login. +storage_overview = Visão geral de armazenamento +quota = Cota +quota.applies_to_org = As seguintes regras de cota se aplicam a esta organização +quota.rule.exceeded = Excedido +quota.rule.no_limit = Ilimitado +quota.sizes.all = Tudo +quota.sizes.repos.all = Repositórios +quota.sizes.repos.public = Repositórios públicos +quota.sizes.repos.private = Repositórios privados +quota.sizes.git.all = Conteúdo Git +quota.sizes.git.lfs = LFS Git +quota.sizes.assets.all = Assets +quota.sizes.assets.attachments.all = Anexos +quota.sizes.assets.attachments.issues = Anexos de issue +quota.sizes.assets.attachments.releases = Anexos de release +quota.sizes.assets.artifacts = Artefatos +quota.sizes.assets.packages.all = Pacotes +quota.sizes.wiki = Wiki [repo] owner=Proprietário @@ -1175,8 +1201,8 @@ template.issue_labels=Etiquetas de issue template.one_item=Deve-se selecionar pelo menos um item de modelo template.invalid=Deve-se selecionar um repositório de modelo -archive.title=Este repositório está arquivado. Você pode visualizar arquivos e cloná-lo, mas não pode fazer push, abrir issues ou pull requests. -archive.title_date=Este repositório foi arquivado em %s. Você pode visualizar arquivos e cloná-lo, mas não pode fazer push, abrir issues ou pull requests. +archive.title=Este repositório está arquivado. Você pode visualizar arquivos e cloná-lo, mas não pode fazer alterações, tais como push, novos issues, pull requests ou comentários. +archive.title_date=Este repositório foi arquivado em %s. Você pode visualizar arquivos e cloná-lo, mas não pode fazer alterações, tais como push, abrir issues, pull requests ou comentários. archive.issue.nocomment=Este repositório está arquivado. Você não pode comentar em issues. archive.pull.nocomment=Este repositório está arquivado. Você não pode comentar em pull requests. @@ -2660,7 +2686,7 @@ pulls.cmd_instruction_checkout_title = Checkout settings.wiki_globally_editable = Permitir que qualquer pessoa edite a wiki settings.transfer_abort_success = A transferência de repositório para %s foi cancelada. settings.enter_repo_name = Digite os nomes do dono e do repositório exatamente neste formato: -issues.blocked_by_user = Você não pode criar uma questão neste repositório porque você foi bloqueado pelo dono do repositório. +issues.blocked_by_user = Você não pode criar issues neste repositório porque você foi bloqueado pelo dono do repositório. settings.new_owner_blocked_doer = Você foi bloqueado pelo novo dono do repositório. settings.wiki_rename_branch_main_notices_1 = NÃO É POSSÍVEL desfazer esta ação. tree_path_not_found_commit = O caminho %[1]s não existe no commit %[2]s @@ -2751,8 +2777,8 @@ release.system_generated = Este anexo foi gerado automaticamente. settings.wiki_branch_rename_failure = Falha ao regularizar o nome do ramo da wiki do repositório. settings.add_collaborator_blocked_them = Não foi possível adicionar o(a) colaborador(a) porque ele(a) bloqueou o(a) proprietário(a) do repositório. settings.thread_id = ID da discussão -issues.edit.already_changed = Não foi possível salvar as alterações desta questão porque o conteúdo foi alterado por outro(a) usuário(a). Atualize a página e tente novamente para evitar sobrescrever as alterações. -pulls.edit.already_changed = Não foi possível salvar as alterações deste pedido de integração porque o conteúdo foi alterado por outro(a) usuário(a). Atualize a página e tente novamente para evitar sobrescrever as alterações. +issues.edit.already_changed = Não foi possível salvar as alterações desta questão. O conteúdo parece já ter sido alterado por outro(a) usuário(a). Atualize a página e tente novamente para evitar sobrescrever estas alterações. +pulls.edit.already_changed = Não foi possível salvar as alterações deste pull request. Parece que o conteúdo já foi alterado por outro(a) usuário(a). Atualize a página e tente novamente para evitar sobrescrever estas alterações. editor.commit_id_not_matching = O arquivo foi alterado durante a edição. Salve as alterações em um novo ramo e realize a mesclagem. blame.ignore_revs = As revisões em .git-blame-ignore-revs foram ignoradas. Clique aqui para retornar à visualização normal. topic.format_prompt = Os tópicos devem começar com um caracter alfanumérico, podem incluir hífens ("-") e pontos ("."), e podem ter até 35 caracteres. As letras devem ser minúsculas. @@ -2767,7 +2793,7 @@ pulls.fast_forward_only_merge_pull_request = Apenas fast-forward pulls.has_merged = Falha: O pull request foi merged, você não pode merge novamente ou mudar o branch destino. issues.author.tooltip.pr = Esse usuário é o autor dessa solicitação de pull. editor.push_out_of_date = O push parece estar desatualizado. -issues.comment.blocked_by_user = Você não pode criar um comentário nesse problema porque você está bloqueado pelo dono do repositório ou pelo autor do problema. +issues.comment.blocked_by_user = Você não pode comentar neste issue porque você foi bloqueado pelo dono do repositório ou pelo autor deste issue. pulls.blocked_by_user = Você não pode criar uma solicitação de pull nesse repositório porque você está bloqueado pelo dono do repositório. mirror_use_ssh.helper = Forgejo irá espelhar o repositório via Git através de SSH e criar um par de chaves para você ao escolher essa opção. Você deverá garantir que a chave pública gerada está autorizada a fazer push para o repositório de destino. Você não pode usar autorização baseada em senha ao escolher essa opção. mirror_denied_combination = Não é possível combinar o uso de chave pública e autenticação baseada em senha. @@ -2873,6 +2899,8 @@ editor.commit_email = Email de commit commits.view_single_diff = Ver modificações neste arquivo introduzidas neste commit pulls.editable = Editável pulls.editable_explanation = Este pull request permite edições de mantenedores. Voçê pode contribuir diretamenta para ele. +issues.reopen.blocked_by_user = Você não pode reabrir este issue porque você foi bloqueado pelo dono do repositório ou pelo criador deste issue. +pulls.comment.blocked_by_user = Você não pode comentar neste pull request porque você foi bloqueado pelo dono do repositório ou pelo autor do pull request. [graphs] component_loading = Carregando %s... @@ -3181,7 +3209,7 @@ orgs.new_orga=Nova organização repos.repo_manage_panel=Gerenciar repositórios repos.unadopted=Repositórios não adotados -repos.unadopted.no_more=Não foram encontrados repositórios não adotados +repos.unadopted.no_more=Não foram encontrados repositórios não adotados. repos.owner=Proprietário(a) repos.name=Nome repos.private=Privado @@ -3837,7 +3865,7 @@ alt.install = Instalar pacote alt.repository = Informação do repositório alt.repository.architectures = Arquiteturas alt.repository.multiple_groups = Este pacote está disponível em múltiplos grupos. -alt.setup = Adicionar um repositório à lista de repositórios conectados (escolha a arquitetura necessária em vez de '_arch_'): +alt.setup = Adicionar um repositório à lista de repositórios conectados (escolha a arquitetura necessária em vez de "_arch_"): [secrets] secrets=Segredos @@ -3998,7 +4026,7 @@ commit_kind = Buscar commits… runner_kind = Pesquisar runners... code_search_unavailable = A pesquisa de código não está disponível no momento. Entre em contato com o administrador do site. milestone_kind = Pesquisar marcos... -union_tooltip = Incluir resultados que coincidam com quaisquer palavras-chave separadas por espaços em branco +union_tooltip = Incluir resultados que correspondam a quaisquer palavras-chave separadas por espaços em branco union = União exact = Exato exact_tooltip = Incluir apenas resultados que correspondam exatamente ao termo de pesquisa diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 7bbb23ee55..bf0bab6fd9 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -112,7 +112,7 @@ preview=Pré-visualizar loading=Carregando… error=Erro -error404=A página que pretende aceder não existe ou não tem autorização para a ver. +error404=A página que pretende aceder não existe, foi removida ou não tem autorização para a ver. go_back=Voltar never=Nunca @@ -206,6 +206,10 @@ table_modal.placeholder.header = Cabeçalho table_modal.placeholder.content = Conteúdo table_modal.label.rows = Linhas table_modal.label.columns = Colunas +link_modal.header = Adicionar uma ligação +link_modal.url = URL +link_modal.description = Descrição +link_modal.paste_reminder = Sugestão: Com um URL na área de transferência, pode colar diretamente no editor para criar uma ligação. [filter] string.asc=A - Z @@ -1061,6 +1065,28 @@ pronouns_custom_label = Pronomes personalizados user_block_yourself = Não se pode bloquear a si próprio. change_username_redirect_prompt.with_cooldown.one = O nome de utilizador antigo estará disponível para todos após um período de espera de %[1]d dia, podendo ainda reivindicar o nome de utilizador antigo durante o período de espera. change_username_redirect_prompt.with_cooldown.few = O nome de utilizador antigo ficará disponível para todos após um período de espera de %[1]d dias, podendo ainda reivindicar o nome de utilizador antigo durante o período de espera. +quota.applies_to_user = As seguintes regras de quotas aplicam-se à sua conta +quota.sizes.assets.artifacts = Artefactos +quota.rule.exceeded.helper = O tamanho total dos objectos para esta regra excedeu a quota. +keep_pronouns_private = Mostrar os pronomes apenas aos utilizadores autenticados +keep_pronouns_private.description = Isto irá ocultar os seus pronomes dos visitantes que não tenham iniciado sessão. +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.all = Ativos +storage_overview = Panorama geral do armazenamento +quota = Quota +quota.applies_to_org = As seguintes regras de quotas aplicam-se a esta organização +quota.rule.exceeded = Excedido +quota.rule.no_limit = Ilimitado +quota.sizes.all = Tudo +quota.sizes.repos.all = Repositórios +quota.sizes.repos.public = Repositórios públicos +quota.sizes.repos.private = Repositórios privados +quota.sizes.git.all = Conteúdo Git +quota.sizes.assets.attachments.all = Anexos +quota.sizes.assets.attachments.issues = Anexos de questões +quota.sizes.assets.attachments.releases = Anexos de lançamentos +quota.sizes.assets.packages.all = Pacotes +quota.sizes.wiki = Wiki [repo] new_repo_helper=Um repositório contém todos os ficheiros do trabalho, incluindo o histórico das revisões. Já tem um hospedado noutro sítio? Migre o repositório. @@ -1187,8 +1213,8 @@ template.issue_labels=Rótulos das questões template.one_item=Tem que escolher pelo menos um item do modelo template.invalid=Tem que escolher um repositório modelo -archive.title=Este repositório está arquivado. Pode ver os ficheiros e cloná-lo, mas não pode fazer envios ou abrir questões ou pedidos de integração. -archive.title_date=Este repositório foi arquivado em %s. Pode ver os ficheiros e cloná-lo, mas não pode fazer envios ou abrir questões/pedidos de integração. +archive.title=Este repositório está arquivado. Pode ver os ficheiros e cloná-lo, mas não pode fazer quaisquer alterações ao seu estado, tais como fazer envios e criar novas questões, pedidos de integração ou comentários. +archive.title_date=Este repositório foi arquivado em %s. Pode ver os ficheiros e cloná-lo, mas não pode fazer quaisquer alterações ao seu estado, tais como fazer envios e criar novas questões, pedidos de integração ou comentários. archive.issue.nocomment=Este repositório está arquivado. Não pode comentar nas questões. archive.pull.nocomment=Este repositório está arquivado. Não pode comentar nos pedidos de integração. @@ -2721,7 +2747,7 @@ find_file.no_matching=Não foi encontrado qualquer ficheiro correspondente error.csv.too_large=Não é possível apresentar este ficheiro por ser demasiado grande. error.csv.unexpected=Não é possível apresentar este ficheiro porque contém um caractere inesperado na linha %d e coluna %d. error.csv.invalid_field_count=Não é possível apresentar este ficheiro porque tem um número errado de campos na linha %d. -issues.blocked_by_user = Não pode criar uma questão neste repositório porque foi bloqueado/a pelo/a proprietário/a do repositório. +issues.blocked_by_user = Não pode criar questões neste repositório porque foi bloqueado(a) pelo(a) proprietário(a) do repositório. issues.num_participants_one = %d participante stars = Favoritos editor.invalid_commit_mail = Email inválido para criar um cometimento. @@ -2787,7 +2813,7 @@ settings.sourcehut_builds.secrets = Segredos settings.matrix.room_id_helper = O ID da Sala pode ser obtido no cliente web Element > Configurações da sala > Avançado > ID interno da sala. Exemplo: %s. settings.web_hook_name_sourcehut_builds = Construções do SourceHut settings.enter_repo_name = Insira o nome do/a proprietário/a e do repositório tal como é apresentado: -issues.comment.blocked_by_user = Não pode criar um comentário nesta questão porque foi bloqueado/a pelo/a proprietário/a ou pelo remetente da questão. +issues.comment.blocked_by_user = Não pode comentar nesta questão porque foi bloqueado(a) pelo(a) proprietário(a) ou pelo autor da questão. pulls.merged_title_desc_one = integrou %[1]d cometimento do ramo %[2]s no ramo %[3]s %[4]s pulls.agit_explanation = Criado usando a sequência de trabalho AGit. AGit deixa os contribuidores proporem alterações usando "git push" sem criar uma derivação ou um ramo novo. settings.new_owner_blocked_doer = O/A novo/a proprietário/a bloqueou-o/a. @@ -2872,6 +2898,11 @@ summary_card_alt = Cartão de resumo do repositório %s release.summary_card_alt = Cartão de resumo de um lançamento com o título "%s" no repositório %s archive.pull.noreview = Este repositório está arquivado. Não é possível rever os pedidos de integração. editor.commit_email = Endereço de email do cometimento +commits.view_single_diff = Ver alterações a este ficheiro introduzidas neste cometimento +pulls.comment.blocked_by_user = Não pode comentar este pedido de integração porque está bloqueado pelo(a) proprietário(a) do repositório ou pelo(a) autor(a) do pedido de integração. +issues.reopen.blocked_by_user = Não pode reabrir esta questão porque está bloqueado pelo(a) proprietário(a) do repositório ou pelo autor da questão. +pulls.editable = Editável +pulls.editable_explanation = Este pedido de integração permite edições dos responsáveis. Pode contribuir diretamente para ele. [graphs] component_loading=A carregar %s... @@ -3007,8 +3038,8 @@ teams.invite.by=Convidado(a) por %s teams.invite.description=Clique no botão abaixo para se juntar à equipa. follow_blocked_user = Não pode seguir esta organização porque esta organização bloqueou-o/a. open_dashboard = Abrir painel de controlo -settings.change_orgname_redirect_prompt.with_cooldown.one = O nome de utilizador antigo estará disponível para todos após um período de espera de %[1]d dia, podendo ainda reivindicar o nome de utilizador antigo durante o período de espera. -settings.change_orgname_redirect_prompt.with_cooldown.few = O nome de utilizador antigo estará disponível para todos após um período de espera de %[1]d dias, podendo ainda reivindicar o nome de utilizador antigo durante o período de espera. +settings.change_orgname_redirect_prompt.with_cooldown.one = O nome antigo da organização estará disponível para todos após um período de espera de %[1]d dia, podendo ainda reivindicar o nome antigo durante o período de espera. +settings.change_orgname_redirect_prompt.with_cooldown.few = O nome antigo da organização estará disponível para todos após um período de espera de %[1]d dias, podendo ainda reivindicar o nome antigo durante o período de espera. [admin] dashboard=Painel de controlo @@ -3835,7 +3866,7 @@ alt.install = Instalar pacote alt.repository = Informação do repositório alt.repository.architectures = Arquiteturas alt.repository.multiple_groups = Este pacote está disponível em vários grupos. -alt.setup = Adicionar um repositório à lista de repositórios ligados (escolha a arquitetura necessária em vez de '_arch_'): +alt.setup = Adicionar um repositório à lista de repositórios ligados (escolha a arquitetura necessária em vez de "_arch_"): [secrets] secrets=Segredos @@ -3956,6 +3987,7 @@ workflow.dispatch.invalid_input_type = Tipo de entrada "%s" inválido. runs.expire_log_message = Os registos foram purgados por serem demasiado antigos. runs.no_workflows.help_no_write_access = Para aprender sobre Forgejo Actions, vejaa documentação. runs.no_workflows.help_write_access = Não sabe como começar com o Forgejo Actions? Consulte o início rápido na documentação do utilizador para escrever a sua primeira sequência de trabalho, depois prepare um executor Forgejo para executar os seus trabalhos. +variables.not_found = Não foi possível encontrar a variável. [projects] type-1.display_name=Planeamento individual diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 74ab9c3943..dc5100481c 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -110,7 +110,7 @@ preview=Предпросмотр loading=Загрузка… error=Ошибка -error404=Cтраница, которую вы пытаетесь открыть, не существует, либо у вас недостаточно прав для ее просмотра. +error404=Cтраница, которую вы пытаетесь открыть, не существует, была удалена, либо у вас недостаточно прав для её просмотра. go_back=Назад never=Никогда @@ -206,6 +206,10 @@ table_modal.header = Создание таблицы table_modal.placeholder.header = Заголовок table_modal.placeholder.content = Содержимое table_modal.label.rows = Кол-во строк +link_modal.header = Добавить ссылку +link_modal.url = Ссылка +link_modal.description = Описание +link_modal.paste_reminder = Имея ссылку в буфере обмена, вы можете вставить её напрямую в текст, чтобы создать ссылку с описанием. [filter] string.asc=A - Я @@ -1063,6 +1067,26 @@ change_username_redirect_prompt.with_cooldown.one = Прежнее имя буд change_username_redirect_prompt.with_cooldown.few = Прежнее имя будет доступно для использования другим пользователям после истечения защиты в %[1]d дней. Вы сможете вернуть его себе во время срока защиты. keep_pronouns_private = Показывать местоимения только зарегистрированным пользователям keep_pronouns_private.description = Местоимения будут скрыты от пользователей, не имеющих учётных записей на сервере. +quota.applies_to_user = Эти ограничения хранилища применяются к вашей учётной записи +quota.applies_to_org = Эти ограничения хранилища применяются к этой организации +quota.sizes.repos.public = Общедоступные репозитории +storage_overview = Использование места +quota = Ограничения хранилища +quota.rule.exceeded = Превышено +quota.rule.exceeded.helper = Суммарный объём объектов в этом правиле превысил допустимый. +quota.rule.no_limit = Неограниченно +quota.sizes.all = Всё +quota.sizes.repos.all = Репозитории +quota.sizes.repos.private = Частные репозитории +quota.sizes.git.all = Содержимое Git +quota.sizes.git.lfs = Git LFS +quota.sizes.wiki = Вики +quota.sizes.assets.packages.all = Пакеты +quota.sizes.assets.all = Объекты +quota.sizes.assets.attachments.all = Все прикреплённые файлы +quota.sizes.assets.attachments.releases = Файлы выпусков +quota.sizes.assets.attachments.issues = Файлы задач +quota.sizes.assets.artifacts = Артефакты [repo] owner=Владелец @@ -2684,7 +2708,7 @@ object_format = Формат объекта clone_in_vscodium = Клонировать в VSCodium mirror_sync = синхронизирован blame.ignore_revs = Правки в .git-blame-ignore-revs проигнорированы. Нажмите здесь, чтобы обойти этот файл и просмотреть авторов полноценно. -issues.blocked_by_user = Невозможно создать задачу в этом репозитории, т.к. вы заблокированы его владельцем. +issues.blocked_by_user = Создание задач невозможно в этом репозитории, т.к. вы заблокированы его владельцем. settings.new_owner_blocked_doer = Вы заблокированы новым владельцем. settings.add_collaborator_blocked_them = Невозможно добавить соучастника, т.к. им заблокирован владелец репозитория. pulls.blocked_by_changed_protected_files_1 = Этот запрос на слияние заблокирован, т.к. им изменяется защищённый файл: @@ -2878,6 +2902,10 @@ editor.commit_email = Эл. почта автора commits.view_single_diff = Посмотреть изменения в этом файле из этого коммита pulls.editable = Изменяемый pulls.editable_explanation = Автор разрешил изменения от соучастников. Вы можете напрямую отправлять в него изменения. +issues.reopen.blocked_by_user = Повторное открытие задачи невозможно, т.к. вы заблокированы владельцем репозитория или автором задачи. +pulls.comment.blocked_by_user = Вы не можете комментировать под этим запросом слияния, т.к. вы заблокированы владельцем репозитория или автором задачи. +issues.filter_no_results = Ничего не нашлось +issues.filter_no_results_placeholder = Попробуйте поискать по-другому. [graphs] component_loading_failed = Не удалось загрузить %s @@ -3699,7 +3727,7 @@ details.project_site=Веб-сайт проекта details.repository_site=Веб-сайт репозитория details.documentation_site=Веб-сайт документации details.license=Лицензия -assets=Ресурсы +assets=Объекты versions=Версии versions.view_all=Показать всё dependency.id=ID diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 85b5db5391..cbb42b3caa 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -77,7 +77,7 @@ write=Skriv preview=Förhandsgranska loading=Laddar… -error404=Sidan du försöker nå finns inte eller så har du inte behörighet att se den. +error404=Sidan du försöker nå finns inte, har tagits bort eller så har du inte behörighet att se den. diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 0639371c0f..18fd4dbb5d 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -207,6 +207,9 @@ table_modal.placeholder.header = Заголовок table_modal.placeholder.content = Вміст table_modal.label.rows = Рядки link_modal.description = Опис +link_modal.url = URL +link_modal.header = Додати посилання +link_modal.paste_reminder = Підказка: якщо скопіювати URL-адресу в буфер обміну, можна створювати посилання, вставляючи її безпосередньо в редакторі. [filter] string.asc = А - Я @@ -1056,7 +1059,7 @@ quota = Квота quota.sizes.repos.private = Приватні репозиторії quota.sizes.repos.public = Публічні репозиторії quota.sizes.git.all = Вміст Git -quota.sizes.git.lfs = Вміст LFS +quota.sizes.git.lfs = Git LFS quota.sizes.assets.packages.all = Пакунки quota.sizes.assets.artifacts = Артефакти quota.sizes.assets.attachments.issues = Вкладення задач @@ -1068,7 +1071,7 @@ quota.sizes.all = Усе quota.sizes.repos.all = Репозиторії quota.applies_to_user = До вашого облікового запису застосовано такі квоти quota.sizes.assets.attachments.all = Вкладення -quota.applies_to_org = До вашої організації застосовано такі квоти +quota.applies_to_org = До цієї організації застосовано такі квоти quota.rule.exceeded = Перевищено [repo] @@ -1786,8 +1789,8 @@ wiki.save_page=Зберегти сторінку wiki.last_commit_info=%s редагував цю сторінку %s wiki.edit_page_button=Редагувати wiki.new_page_button=Нова сторінка -wiki.file_revision=Ревізії сторінки -wiki.wiki_page_revisions=Ревізії вікі сторінок +wiki.file_revision=Версія сторінки +wiki.wiki_page_revisions=Версії сторінок wiki.back_to_wiki=Повернутись на сторінку Вікі wiki.delete_page_button=Видалити сторінку wiki.page_already_exists=Вікі-сторінка з таким самим ім'ям вже існує. @@ -2050,7 +2053,7 @@ settings.event_issue_assign_desc=Задачу призначено або ска settings.event_issue_label=Мітки settings.event_issue_label_desc=Додавання або видалення міток задач. settings.event_issue_milestone=Задача з етапом -settings.event_issue_milestone_desc=Задача призначена на етап або видалена з етапу. +settings.event_issue_milestone_desc=Етап призначено, видалено або змінено. settings.event_issue_comment=Коментарі settings.event_issue_comment_desc=Коментар задачі створено, видалено чи відредаговано. settings.event_header_pull_request=Події запиту на злиття @@ -2060,8 +2063,8 @@ settings.event_pull_request_assign=Призначення settings.event_pull_request_assign_desc=Запит про злиття призначено або скасовано. settings.event_pull_request_label=Мітки settings.event_pull_request_label_desc=Мітки запиту на злиття оновлено або очищено. -settings.event_pull_request_milestone=Запит на злиття призначений на етап -settings.event_pull_request_milestone_desc=Запит на злиття призначений на етап або видалений з етапу. +settings.event_pull_request_milestone=Етапи +settings.event_pull_request_milestone_desc=Етап призначено, видалено або змінено. settings.event_pull_request_comment=Коментарі settings.event_pull_request_comment_desc=Коментар запиту на злиття створено, відредаговано чи видалено. settings.event_pull_request_review=Відгуки @@ -2336,7 +2339,7 @@ issues.author_helper = Цей користувач - автор. issues.close = Закрити задачу issues.role.owner_helper = Цей користувач є власником цього репозиторію. settings.mirror_settings.docs.more_information_if_disabled = Докладніше про push та pull дзеркала можна дізнатися тут: -issues.comment.blocked_by_user = Ви не можете створити коментар до цієї задачі, оскільки вас заблокував власник репозиторію або автор цієї задачі. +issues.comment.blocked_by_user = Ви не можете коментувати цю задачу, оскільки вас заблокував власник репозиторію або автор цієї задачі. editor.add_file = Додати файл from_comment = (коментар) editor.add = Додати %s @@ -2579,6 +2582,15 @@ n_release_few = %s випусків release.releases_for = Випуски %s release.type_attachment = Вкладення n_release_one = %s випуск +pulls.comment.blocked_by_user = Ви не можете коментувати цей запит на злиття, оскільки вас заблокував власник репозиторію або автор запиту на злиття. +issues.reopen.blocked_by_user = Ви не можете знову відкрити цю задачу, оскільки вас заблокував власник репозиторію або автор задачі. +settings.actions_desc = Увімкнути вбудовані конвеєри CI/CD з Діями Forgejo +tree_path_not_found_commit = Шлях %[1]s не існує в коміті %[2]s +pulls.blocked_by_user = Ви не можете створити запит на злиття в цьому репозиторії, оскільки вас заблокував власник репозиторію. +issues.blocked_by_user = Ви не можете створювати задачі в цьому репозиторії, оскільки вас заблокував власник репозиторію. +activity.commit = К-ть комітів +issues.filter_no_results_placeholder = Спробуйте змінити пошукові фільтри. +issues.filter_no_results = Нічого не знайдено [graphs] contributors.what = внески @@ -2703,6 +2715,7 @@ teams.invite.title = Вас запрошено приєднатися до ко form.name_reserved = Назву організації «%s» зарезервовано. settings.change_orgname_redirect_prompt.with_cooldown.one = Стара назва буде доступна всім після періоду захисту, який триватиме %[1]d день. Протягом періоду захисту ви ще можете повернути стару назву. settings.change_orgname_redirect_prompt.with_cooldown.few = Стара назва буде доступна всім після періоду захисту, який триватиме %[1]d днів. Протягом періоду захисту ви ще можете повернути стару назву. +teams.none_access = Немає доступу [admin] dashboard=Панель управління @@ -2966,7 +2979,7 @@ auths.tip.google_plus=Отримайте облікові дані клієнт auths.tip.openid_connect=Використовуйте OpenID Connect Discovery URL (/.well-known/openid-configuration) для автоматичної настройки входу OAuth auths.tip.twitter=Перейдіть на %s, створіть програму і переконайтеся, що включена опція «Дозволити цю програму для входу в систему за допомогою Twitter» auths.tip.discord=Зареєструйте новий додаток на %s -auths.tip.yandex=Створіть новий додаток на %s. Виберіть наступні дозволи з «Yandex.Passport API»: «Доступ до адреси електронної пошти», «Доступ до аватара» і «Доступ до імені користувача, імені та прізвища, статі» +auths.tip.yandex=Створіть новий додаток на %s. У розділі «Yandex.Passport API» виберіть такі дозволи: «Доступ до адреси електронної пошти», «Доступ до аватара» і «Доступ до імені користувача, імені та прізвища, статі» auths.tip.mastodon=Введіть URL спеціального екземпляра для екземпляра mastodon, який ви хочете автентифікувати за допомогою (або використовувати за замовчуванням) auths.edit=Редагувати джерело автентифікації auths.activated=Це джерело авторизації активоване @@ -3188,6 +3201,7 @@ users.reserved = Зарезервовано systemhooks.desc = Вебхуки автоматично сповіщають HTTP-сервер POST-запитами, коли в Forgejo відбуваються певні події. Вказані тут вебхуки спрацьовуватимуть для всіх репозиторіїв системи, тож врахуйте всі ймовірні наслідки для швидкодії. Докладніше — в посібнику з вебхуків. dashboard.cleanup_actions = Очистити прострочені журнали й артефакти від дій dashboard.gc_lfs = Виконати очистку сміття метаоб'єктів LFS +dashboard.new_version_hint = Вийшла %s версія Forgejo, ви використовуєте %s. Докладніше читайте у блозі. [action] @@ -3296,8 +3310,8 @@ arch.version.conflicts = Суперечки arch.version.replaces = Заміни arch.version.provides = Надає arch.version.groups = Група -conda.install = Аби встановити пакунок, використовуючи Conda, запустіть команду: -cargo.install = Аби встановити пакунок, використовуючи Cargo, запустіть команду: +conda.install = Аби встановити пакунок, використовуючи Conda, виконайте команду: +cargo.install = Аби встановити пакунок, використовуючи Cargo, виконайте команду: versions.view_all = Переглянути всі generic.download = Завантажте пакунок із командного рядка: details = Подробиці @@ -3305,7 +3319,7 @@ arch.version.optdepends = Необовʼязково залежить installation = Установлення details.license = Ліцензія filter.type.all = Усі -conan.install = Аби встановити пакунок, використовуючи Conan, запустіть команду: +conan.install = Аби встановити пакунок, використовуючи Conan, виконайте команду: container.layers = Шари образу details.project_site = Вебсторінка проєкту details.documentation_site = Вебсторінка документації @@ -3314,12 +3328,12 @@ requirements = Вимоги dependencies = Залежності empty.repo = Ви опублікували пакунок, але він не показаний тут? Перейдіть до налаштувань пакунків та привʼяжіть його до цього репозиторію. alpine.repository = Про репозиторій -alpine.install = Аби встановити цей пакунок, запустіть команду: -cran.install = Аби встановити пакунок, запустіть команду: +alpine.install = Аби встановити цей пакунок, виконайте команду: +cran.install = Аби встановити пакунок, виконайте команду: composer.dependencies.development = Залежності розробки container.labels.key = Ключ container.labels.value = Значення -composer.install = Аби встановити пакунок, використовуючи Composer, запустіть команду: +composer.install = Аби встановити пакунок, використовуючи Composer, виконайте команду: debian.repository.components = Складові filter.container.tagged = Відмічений filter.container.untagged = Невідмічений @@ -3329,20 +3343,20 @@ arch.pacman.sync = Синхронізуйте пакунок з pacman: arch.pacman.conf = Додайте сервер з повʼязаним дострибутивом та архітектурою до /etc/pacman.conf : arch.version.properties = Властивості версії arch.version.description = Опис -chef.install = Аби встановити пакунок, запустіть команду: +chef.install = Аби встановити пакунок, виконайте команду: container.details.platform = Платформа container.details.type = Тип образу container.pull = Завантажити образ із командного рядка: details.repository_site = Вебсторінка репозиторію composer.dependencies = Залежності -debian.install = Аби встановити пакунок, запустіть команду: +debian.install = Аби встановити пакунок, виконайте команду: debian.repository = Про репозиторій debian.repository.distributions = Дистрибутиви alpine.repository.architectures = Архітектури arch.version.depends = Залежить go.install = Встановити пакунок із командного рядка: debian.repository.architectures = Архітектури -helm.install = Аби встановити пакунок, запустіть команду: +helm.install = Аби встановити пакунок, виконайте команду: keywords = Ключові слова assets = Ресурси versions = Версії @@ -3377,6 +3391,17 @@ alt.install = Встановити пакунок alt.registry = Налаштуйте цей реєстр із командного рядка: debian.registry = Налаштуйте цей реєстр із командного рядка: debian.registry.info = Виберіть $distribution і $component зі списку нижче. +npm.install = Аби встановити пакунок, використовуючи npm, виконайте команду: +alt.registry.install = Щоб установити пакунок, виконайте команду: +swift.install2 = і виконайте команду: +rubygems.install = Аби встановити пакунок, використовуючи gem, виконайте команду: +alt.setup = Додайте репозиторій до списку підключених репозиторіїв (виберіть потрібну архітектуру замість «_arch_»): +pypi.install = Аби встановити пакунок, використовуючи pip, виконайте команду: +nuget.install = Аби встановити пакунок, використовуючи NuGet, виконайте команду: +pub.install = Аби встановити пакунок, використовуючи Dart, виконайте команду: +rpm.install = Щоб установити пакунок, виконайте команду: +maven.install = Для використання пакунка включіть у блок dependencies у файлі pom.xml таке: +vagrant.install = Щоб додати скриньку Vagrant, виконайте команду: [secrets] deletion = Видалити секрет @@ -3454,6 +3479,9 @@ need_approval_desc = Потрібне схвалення для запуску variables.not_found = Не вдалося знайти змінну. runners.task_list.done_at = Завершено runners.last_online = Востаннє в мережі +runs.no_workflows.help_no_write_access = Щоб дізнатися більше про Дії Forgejo, читайте документацію. +runs.no_workflows.help_write_access = Не знаєте, як почати роботу з Діями Forgejo? Перегляньте посібник для початківців у документації, щоб написати свій перший робочий потік, а потім налаштуйте ранер Forgejo для виконання завдань. +unit.desc = Керування вбудованими конвеєрами CI/CD з Діями Forgejo. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index e7d2ac973a..b9fbc4b0ff 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -2750,8 +2750,8 @@ rss.must_be_on_branch = 您必须处于一个分支上才能拥有一个RSS订 admin.manage_flags = 管理标志 admin.failed_to_replace_flags = 替换仓库标志失败 clone_in_vscodium = 在 VSCodium 中克隆 -issues.blocked_by_user = 你无法在此仓库创建工单,因为你已被仓库所有者屏蔽。 -issues.comment.blocked_by_user = 你无法对此工单进行评论,因为你已被仓库所有者或此工单的发布者屏蔽。 +issues.blocked_by_user = 由于你已被仓库所有者屏蔽,你无法在此仓库创建工单。 +issues.comment.blocked_by_user = 因为你已被仓库所有者或工单作者屏蔽,你无法对此工单进行评论。 settings.wiki_rename_branch_main_desc = 将百科内部使用的分支重命名为“%s”。此更改是永久性的且不可撤销。 editor.invalid_commit_mail = 用于创建提交的邮件地址无效。 pulls.blocked_by_user = 你无法在此仓库上创建合并请求,因为您已被仓库所有者屏蔽。 @@ -2901,6 +2901,10 @@ archive.pull.noreview = 此仓库已存档,您无法评审合并请求。 commits.view_single_diff = 查看该提交对本文件的更改 pulls.editable = 可编辑 pulls.editable_explanation = 此合并请求允许维护者进行编辑。你可以直接向其贡献。 +issues.reopen.blocked_by_user = 由于你已被仓库所有者或工单作者屏蔽,你不能重新打开此工单。 +pulls.comment.blocked_by_user = 由于你已被仓库所有者或合并请求作者屏蔽,你不能在此合并请求发表评论。 +issues.filter_no_results = 无结果 +issues.filter_no_results_placeholder = 尝试调整搜索筛选条件。 [graphs] component_loading=正在加载 %s… @@ -3860,7 +3864,7 @@ search_in_external_registry = 在 %s 中搜索 alt.registry.install = 要安装此软件包,请运行以下命令: alt.install = 安装软件包 alt.registry = 通过命令行配置此注册表: -alt.setup = 添加一个仓库到连接的仓库列表(选择必要的架构而非'_arch_'): +alt.setup = 添加一个仓库到连接的仓库列表(选择必要的架构而非“_arch_”): alt.repository = 仓库信息 alt.repository.architectures = 架构 alt.repository.multiple_groups = 此软件包在多个组中可用。 diff --git a/options/locale_next/locale_cs-CZ.json b/options/locale_next/locale_cs-CZ.json index 8d028b8367..8ae2abdb93 100644 --- a/options/locale_next/locale_cs-CZ.json +++ b/options/locale_next/locale_cs-CZ.json @@ -1,11 +1,19 @@ { - "repo.pulls.merged_title_desc": { - "one": "sloučil %[1]d commit z %[2]s do %[3]s %[4]s", - "other": "sloučil %[1]d commity z větve %[2]s do větve %[3]s před %[4]s" - }, - "repo.pulls.title_desc": { - "one": "žádá o sloučení %[1]d commitu z %[2]s do %[3]s", - "other": "chce sloučit %[1]d commity z větve %[2]s do %[3]s" - }, - "search.milestone_kind": "Hledat milníky..." + "repo.pulls.merged_title_desc": { + "one": "sloučil/a %[1]d revizi z větve %[2]s do větve %[3]s %[4]s", + "few": "sloučil/a %[1]d revize z větve %[2]s do větve %[3]s před %[4]s", + "other": "sloučil/a %[1]d revizí z větve %[2]s do větve %[3]s před %[4]s" + }, + "repo.pulls.title_desc": { + "one": "žádá o sloučení %[1]d revize z větve %[2]s do větve %[3]s", + "few": "žádá o sloučení %[1]d revizí z větve %[2]s do větve %[3]s", + "other": "žádá o sloučení %[1]d revizí z větve %[2]s do větve %[3]s" + }, + "search.milestone_kind": "Hledat milníky…", + "home.welcome.no_activity": "Žádná aktivita", + "home.welcome.activity_hint": "Váš feed je zatím prázdný. Zde se budou zobrazovat vaše akce a aktivity z repozitářů, které sledujete.", + "home.explore_repos": "Procházet repozitáře", + "home.explore_users": "Procházet uživatele", + "home.explore_orgs": "Procházet organizace", + "incorrect_root_url": "Tato instance Forgejo je nastavena tak, aby běžela na adrese „%s“. Vy si momentálně prohlížíte Forgejo na jiné adrese, což může způsobit rozbití některých částí aplikace. Správná adresa je ovládána správci Forgejo pomocí nastavení ROOT_URL v souboru app.ini." } diff --git a/options/locale_next/locale_da.json b/options/locale_next/locale_da.json index 0c2c9a25ea..36eebe9c93 100644 --- a/options/locale_next/locale_da.json +++ b/options/locale_next/locale_da.json @@ -1,3 +1,17 @@ { - "search.milestone_kind": "Søg milepæle..." + "search.milestone_kind": "Søg milepæle...", + "repo.pulls.title_desc": { + "one": "ønsker at flette %[1]d commit fra %[2]s til %[3]s", + "other": "ønsker at flette %[1]d commits fra %[2]s til %[3]s" + }, + "repo.pulls.merged_title_desc": { + "one": "flettet %[1]d commit fra %[2]s til %[3]s %[4]s", + "other": "flettet %[1]d commits fra %[2]s til %[3]s %[4]s" + }, + "incorrect_root_url": "Denne Forgejo-instans er konfigureret til at blive serveret på \"%s\". Du ser i øjeblikket Forgejo gennem en anden URL, hvilket kan få dele af applikationen til at gå i stykker. Den kanoniske URL styres af Forgejo-administratorer via indstillingen ROOT_URL i app.ini.", + "home.welcome.no_activity": "Ingen aktivitet", + "home.welcome.activity_hint": "Der er intet i dit feed endnu. Dine handlinger og aktivitet fra depoterne, som du ser, vises her.", + "home.explore_repos": "Udforsk depoter", + "home.explore_users": "Udforsk brugere", + "home.explore_orgs": "Udforsk organisationer" } diff --git a/options/locale_next/locale_de-DE.json b/options/locale_next/locale_de-DE.json index f4a15ecdb9..a39f5b12bc 100644 --- a/options/locale_next/locale_de-DE.json +++ b/options/locale_next/locale_de-DE.json @@ -1,11 +1,16 @@ { - "repo.pulls.merged_title_desc": { - "one": "hat %[1]d Commit von %[2]s nach %[3]s %[4]s zusammengeführt", - "other": "hat %[1]d Commits von %[2]s nach %[3]s %[4]s zusammengeführt" - }, - "repo.pulls.title_desc": { - "one": "möchte %[1]d Commit von %[2]s nach %[3]s zusammenführen", - "other": "möchte %[1]d Commits von %[2]s nach %[3]s zusammenführen" - }, - "search.milestone_kind": "Meilensteine suchen …" + "repo.pulls.merged_title_desc": { + "one": "hat %[1]d Commit von %[2]s nach %[3]s %[4]s zusammengeführt", + "other": "hat %[1]d Commits von %[2]s nach %[3]s %[4]s zusammengeführt" + }, + "repo.pulls.title_desc": { + "one": "möchte %[1]d Commit von %[2]s nach %[3]s zusammenführen", + "other": "möchte %[1]d Commits von %[2]s nach %[3]s zusammenführen" + }, + "search.milestone_kind": "Meilensteine suchen …", + "home.welcome.no_activity": "Keine Aktivität", + "home.explore_repos": "Repositorys erkunden", + "home.explore_users": "Benutzer erkunden", + "home.explore_orgs": "Organisationen erkunden", + "home.welcome.activity_hint": "Es gibt noch nichts in deinem Feed. Deine Aktionen und Aktivitäten aus Repositorys, die du beobachtest, werden hier auftauchen." } diff --git a/options/locale_next/locale_fa-IR.json b/options/locale_next/locale_fa-IR.json index 0a703d22d7..78d6944203 100644 --- a/options/locale_next/locale_fa-IR.json +++ b/options/locale_next/locale_fa-IR.json @@ -1,8 +1,16 @@ { - "repo.pulls.merged_title_desc": { - "other": "%[1]d کامیت ادغام شده از %[2]s به %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "other": "قصد ادغام %[1]d تغییر را از %[2]s به %[3]s دارد" - } + "repo.pulls.merged_title_desc": { + "one": "واکشی %[1]d سپرده از %[2]s به %[3]s %[4]s", + "other": "واکشی %[1]d سپرده‌ها از %[2]s به %[3]s %[4]s" + }, + "repo.pulls.title_desc": { + "one": "خواست واکشی %[1]d سپرده را از %[2]s به %[3]s دارد", + "other": "خواست واکشی %[1]d سپرده‌ها را از %[2]s به %[3]s دارد" + }, + "home.welcome.activity_hint": "هنوز چیزی در آزوغه شما نیست. در اینجا کنش‌ها و کوشش‌های شما در مخازنی که نگاه‌بانی می‌کنید نمایش داده خواهد شد.", + "search.milestone_kind": "جستجو نقاط عطف...", + "home.welcome.no_activity": "بدون کوشش", + "home.explore_repos": "کاوش مخازن", + "home.explore_users": "کاوش کاربران", + "home.explore_orgs": "کاوش سازمان‌ها" } diff --git a/options/locale_next/locale_lv-LV.json b/options/locale_next/locale_lv-LV.json index a16e3aaf8a..a0d57309ff 100644 --- a/options/locale_next/locale_lv-LV.json +++ b/options/locale_next/locale_lv-LV.json @@ -1,11 +1,19 @@ { - "repo.pulls.merged_title_desc": { - "one": "iekļāva %[1]d iesūtījumu no %[2]s %[3]s %[4]s", - "other": "Iekļāva %[1]d iesūtījumus no %[2]s zarā %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "one": "vēlas iekļaut %[1]d iesūtījumu no %[2]s %[3]s", - "other": "vēlas iekļaut %[1]d iesūtījumus no %[2]s zarā %[3]s" - }, - "search.milestone_kind": "Meklēt atskaites punktus..." + "repo.pulls.merged_title_desc": { + "zero": "iekļāva %[1]d iesūtījumu no %[2]s %[3]s %[4]s", + "one": "Iekļāva %[1]d iesūtījumu no %[2]s zarā %[3]s %[4]s", + "other": "Iekļāva %[1]d iesūtījumus no %[2]s zarā %[3]s %[4]s" + }, + "repo.pulls.title_desc": { + "zero": "vēlas iekļaut %[1]d iesūtījumu no %[2]s %[3]s", + "one": "vēlas iekļaut %[1]d iesūtījumu no %[2]s zarā %[3]s", + "other": "vēlas iekļaut %[1]d iesūtījumus no %[2]s zarā %[3]s" + }, + "search.milestone_kind": "Meklēt atskaites punktus...", + "home.welcome.no_activity": "Nav darbību", + "home.welcome.activity_hint": "Barotnē pagaidām nekā nav. Šeit parādīsies darbības un vēroto glabātavu notikumi.", + "home.explore_repos": "Izpētīt glabātavas", + "home.explore_users": "Izpētīt lietotājus", + "home.explore_orgs": "Izpētīt apvienības", + "incorrect_root_url": "Šis Forgejo serveris ir konfigurēts darboties \"%s\". Pašlaik Forgejo tiek apmeklēta ar atšķirīgu URL, kas var radīt atsevišķu lietotnes daļu salūšanu. Kanonisko URL pārrauga Forgejo pārvaldītāji ar app.ini iestatījumu ROOT_URL." } diff --git a/options/locale_next/locale_nds.json b/options/locale_next/locale_nds.json index 564302820a..a587997da3 100644 --- a/options/locale_next/locale_nds.json +++ b/options/locale_next/locale_nds.json @@ -1,11 +1,17 @@ { - "repo.pulls.merged_title_desc": { - "one": "hett %[1]d Kommitteren vun %[2]s na %[3]s %[4]s tosamenföhrt", - "other": "hett %[1]d Kommitterens vun %[2]s na %[3]s %[4]s tosamenföhrt" - }, - "repo.pulls.title_desc": { - "one": "will %[1]d Kommitteren vun %[2]s na %[3]s tosamenföhren", - "other": "will %[1]d Kommitterens vun %[2]s na %[3]s tosamenföhren" - }, - "search.milestone_kind": "In Markstenen söken …" + "repo.pulls.merged_title_desc": { + "one": "hett %[4]s %[1]d Kommitteren vun %[2]s na %[3]s tosamenföhrt", + "other": "hett %[4]s %[1]d Kommitterens vun %[2]s na %[3]s tosamenföhrt" + }, + "repo.pulls.title_desc": { + "one": "will %[1]d Kommitteren vun %[2]s na %[3]s tosamenföhren", + "other": "will %[1]d Kommitterens vun %[2]s na %[3]s tosamenföhren" + }, + "search.milestone_kind": "In Markstenen söken …", + "home.explore_users": "Brukers utförsken", + "home.explore_orgs": "Vereenigungen utförsken", + "home.explore_repos": "Repositoriums utförsken", + "home.welcome.no_activity": "Keen Doon", + "home.welcome.activity_hint": "In dienem Schuuv is noch nix. Dien Doon un dat Doon vun Repositoriums, wat du beluurst, word hier wiest worden.", + "incorrect_root_url": "Deese Forgejo-Instanz is inricht, unner »%s« besöcht to worden. Du bekiekst Forgejo jüüst dör een anner URL, wat daarto föhren kann, dat ’t deelwies nich richtig warkt. De kanonisk URL word vun de Forgejo-Chefs över de ROOT_URL-Instellen in de app.ini kuntrolleert." } diff --git a/options/locale_next/locale_nl-NL.json b/options/locale_next/locale_nl-NL.json index fbb78e9280..cfa80104e8 100644 --- a/options/locale_next/locale_nl-NL.json +++ b/options/locale_next/locale_nl-NL.json @@ -1,11 +1,17 @@ { - "repo.pulls.merged_title_desc": { - "one": "heeft %[1]d commit van %[2]s samengevoegd in %[3]s %[4]s", - "other": "heeft %[1]d commits samengevoegd van %[2]s naar %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "one": "wilt %[1]d commit van %[2]s samenvoegen in %[3]s", - "other": "wilt %[1]d commits van %[2]s samenvoegen met %[3]s" - }, - "search.milestone_kind": "Zoek mijlpalen..." + "repo.pulls.merged_title_desc": { + "one": "heeft %[1]d commit van %[2]s samengevoegd in %[3]s %[4]s", + "other": "heeft %[1]d commits samengevoegd van %[2]s naar %[3]s %[4]s" + }, + "repo.pulls.title_desc": { + "one": "wilt %[1]d commit van %[2]s samenvoegen in %[3]s", + "other": "wilt %[1]d commits van %[2]s samenvoegen met %[3]s" + }, + "search.milestone_kind": "Zoek mijlpalen...", + "home.welcome.no_activity": "Geen activiteit", + "home.welcome.activity_hint": "Er staat nog niets in uw feed. Uw acties en activiteiten van repositories die u bekijkt zullen hier verschijnen.", + "home.explore_repos": "Ontdek repositories", + "home.explore_users": "Ontdek gebruikers", + "home.explore_orgs": "Ontdek organisaties", + "incorrect_root_url": "Deze Forgejo-instantie is geconfigureerd om geserveerd te worden op \"%s\". U bekijkt Forgejo momenteel via een andere URL, waardoor onderdelen van de applicatie kunnen breken. De canonieke URL kan worden gewijzigd door Forgejo admins via de ROOT_URL instelling in de app.ini." } diff --git a/options/locale_next/locale_pt-BR.json b/options/locale_next/locale_pt-BR.json index 4de44582a5..a66d679e96 100644 --- a/options/locale_next/locale_pt-BR.json +++ b/options/locale_next/locale_pt-BR.json @@ -1,11 +1,19 @@ { - "repo.pulls.merged_title_desc": { - "one": "mesclou %[1]d commit de %[2]s em %[3]s %[4]s", - "other": "mesclou %[1]d commits de %[2]s em %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "one": "quer mesclar %[1]d commit de %[2]s em %[3]s", - "other": "quer mesclar %[1]d commits de %[2]s em %[3]s" - }, - "search.milestone_kind": "Pesquisar marcos..." + "repo.pulls.merged_title_desc": { + "one": "mesclou %[1]d commit de %[2]s em %[3]s %[4]s", + "many": "mesclou %[1]d commits de %[2]s em %[3]s %[4]s", + "other": "" + }, + "repo.pulls.title_desc": { + "one": "quer mesclar %[1]d commit de %[2]s em %[3]s", + "many": "quer mesclar %[1]d commits de %[2]s em %[3]s", + "other": "" + }, + "search.milestone_kind": "Pesquisar marcos...", + "home.welcome.no_activity": "Sem atividade", + "home.welcome.activity_hint": "Ainda não tem nada no seu feed. Suas ações e atividade dos seus repositórios vigiados aparecerão aqui.", + "home.explore_repos": "Explorar repositórios", + "home.explore_users": "Explorar usuários", + "home.explore_orgs": "Explorar organizações", + "incorrect_root_url": "Esta instância do Forgejo está configurada para o endereço \"%s\". Você está atualmente vendo o Forgejo através de uma URL diferente, o que pode causar erros em algumas partes da aplicação. A URL oficial é controlada pela administração do Forgejo através da configuração ROOT_URL no arquivo app.ini." } diff --git a/options/locale_next/locale_pt-PT.json b/options/locale_next/locale_pt-PT.json index cf72ef3e09..4d58ffff73 100644 --- a/options/locale_next/locale_pt-PT.json +++ b/options/locale_next/locale_pt-PT.json @@ -1,11 +1,18 @@ { - "repo.pulls.merged_title_desc": { - "one": "integrou %[1]d cometimento do ramo %[2]s no ramo %[3]s %[4]s", - "other": "integrou %[1]d cometimento(s) do ramo %[2]s no ramo %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "one": "quer integrar %[1]d cometimento do ramo %[2]s no ramo %[3]s", - "other": "quer integrar %[1]d cometimento(s) do ramo %[2]s no ramo %[3]s" - }, - "search.milestone_kind": "Procurar etapas..." + "repo.pulls.merged_title_desc": { + "one": "integrou %[1]d cometimento do ramo %[2]s no ramo %[3]s %[4]s", + "many": "integrou %[1]d cometimentos do ramo %[2]s no ramo %[3]s %[4]s", + "other": "" + }, + "repo.pulls.title_desc": { + "one": "quer integrar %[1]d cometimento do ramo %[2]s no ramo %[3]s", + "many": "quer integrar %[1]d cometimentos do ramo %[2]s no ramo %[3]s", + "other": "" + }, + "search.milestone_kind": "Procurar etapas...", + "home.welcome.no_activity": "Sem atividade", + "home.welcome.activity_hint": "Ainda não há nada no seu feed. As suas operações e a atividade dos repositórios que vigia aparecerão aqui.", + "home.explore_repos": "Explorar repositórios", + "home.explore_users": "Explorar utilizadores", + "home.explore_orgs": "Explorar organizações" } diff --git a/options/locale_next/locale_ru-RU.json b/options/locale_next/locale_ru-RU.json index 36f54f405b..3db0072760 100644 --- a/options/locale_next/locale_ru-RU.json +++ b/options/locale_next/locale_ru-RU.json @@ -1,11 +1,19 @@ { - "repo.pulls.merged_title_desc": { - "one": "слит %[1]d коммит из %[2]s в %[3]s %[4]s", - "many": "слито %[1]d коммит(ов) из %[2]s в %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "one": "хочет влить %[1]d коммит из %[2]s в %[3]s", - "many": "хочет влить %[1]d коммит(ов) из %[2]s в %[3]s" - }, - "search.milestone_kind": "Найти этапы..." + "repo.pulls.merged_title_desc": { + "one": "слит %[1]d коммит из %[2]s в %[3]s %[4]s", + "few": "слито %[1]d коммита из %[2]s в %[3]s %[4]s", + "many": "слито %[1]d коммитов из %[2]s в %[3]s %[4]s" + }, + "repo.pulls.title_desc": { + "one": "хочет влить %[1]d коммит из %[2]s в %[3]s", + "few": "хочет влить %[1]d коммита из %[2]s в %[3]s", + "many": "хочет влить %[1]d коммитов из %[2]s в %[3]s" + }, + "search.milestone_kind": "Найти этапы...", + "home.explore_repos": "Каталог репозиториев", + "home.explore_users": "Каталог пользователей", + "home.explore_orgs": "Каталог организаций", + "home.welcome.activity_hint": "В вашей ленте пока ничего нет. Ваши действия и активность из отслеживаемых вами репозиториев будут отображены здесь.", + "home.welcome.no_activity": "Нет событий", + "incorrect_root_url": "Этот сервер Forgejo расположен по адресу «%s», но вы просматриваете страницу с другого адреса. Это может приводить к поломкам частей приложения. Канонический адрес указывается администратором сервера в файле конфигурации app.ini - ROOT_URL." } diff --git a/options/locale_next/locale_uk-UA.json b/options/locale_next/locale_uk-UA.json index ce37b9bef3..f76e502124 100644 --- a/options/locale_next/locale_uk-UA.json +++ b/options/locale_next/locale_uk-UA.json @@ -1,11 +1,19 @@ { - "repo.pulls.merged_title_desc": { - "one": "об'єднав %[1]d коміт з %[2]s в %[3]s %[4]s", - "many": "об'єднав %[1]d комітів з %[2]s в %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "one": "хоче об'єднати %[1]d коміт з %[2]s в %[3]s", - "many": "хоче об'єднати %[1]d комітів з %[2]s в %[3]s" - }, - "search.milestone_kind": "Шукати віхи..." + "repo.pulls.merged_title_desc": { + "one": "об'єднує %[1]d коміт з %[2]s в %[3]s %[4]s", + "few": "об'єднує %[1]d коміти з %[2]s в %[3]s %[4]s", + "many": "об'єднує %[1]d комітів з %[2]s в %[3]s %[4]s" + }, + "repo.pulls.title_desc": { + "one": "хоче об'єднати %[1]d коміт з %[2]s в %[3]s", + "few": "хоче об'єднати %[1]d коміти з %[2]s в %[3]s", + "many": "хоче об'єднати %[1]d комітів з %[2]s в %[3]s" + }, + "search.milestone_kind": "Шукати етапи...", + "home.welcome.activity_hint": "У вашій стрічці ще нічого немає. Тут з'являтимуться ваші дії та активність із відстежуваних репозиторіїв.", + "home.welcome.no_activity": "Немає подій", + "home.explore_repos": "Огляд репозиторіїв", + "home.explore_users": "Огляд користувачів", + "home.explore_orgs": "Огляд організацій", + "incorrect_root_url": "Цей екземпляр Forgejo налаштовано на відвідування з «%s». Зараз ви переглядаєте Forgejo за іншою URL-адресою, що може призвести до збоїв деяких частин програми. Канонічна URL-адреса встановлюється адміністраторами Forgejo за допомогою параметра ROOT_URL у файлі app.ini." } diff --git a/options/locale_next/locale_zh-CN.json b/options/locale_next/locale_zh-CN.json index 10b1c9a4be..944b3dbf47 100644 --- a/options/locale_next/locale_zh-CN.json +++ b/options/locale_next/locale_zh-CN.json @@ -1,9 +1,11 @@ { - "repo.pulls.merged_title_desc": { - "other": "于 %[4]s 将 %[1]d 次代码提交从 %[2]s合并至 %[3]s" - }, - "repo.pulls.title_desc": { - "other": "请求将 %[1]d 次代码提交从 %[2]s 合并至 %[3]s" - }, - "search.milestone_kind": "搜索里程碑…" + "repo.pulls.merged_title_desc": "于 %[4]s 将 %[1]d 次代码提交从 %[2]s合并至 %[3]s", + "repo.pulls.title_desc": "请求将 %[1]d 次代码提交从 %[2]s 合并至 %[3]s", + "search.milestone_kind": "搜索里程碑…", + "home.welcome.no_activity": "无活动", + "home.welcome.activity_hint": "您的订阅中还没有任何内容。您关注的仓库中的操作和活动将显示在此处。", + "home.explore_repos": "探索仓库", + "home.explore_users": "探索用户", + "home.explore_orgs": "探索组织", + "incorrect_root_url": "此 Forgejo 实例配置为在“%s”上提供服务。您当前正在通过不同的网址查看 Forgejo,这可能会导致应用程序的某些部分损坏。Forgejo 管理员通过 app.ini 中的 ROOT_URL 设置控制规范网址。" } From 3de904c9632871e72224db819bc241b69d9ff455 Mon Sep 17 00:00:00 2001 From: christopher-besch Date: Fri, 7 Mar 2025 17:54:26 +0000 Subject: [PATCH 61/79] feat: parse inline attachments for incoming mail (#7136) - Some email clients send inline attachments using the `multipart/related` Mime-Type and enmime collects these in the `Envelope.OtherParts` list; until now only Envelope.Attachments and Envelope.Inline were considered while parsing incoming mail. - As some email clients send attachments without filename, especially in the multipart/related case, this PR implements `constructFilename`, which guesses the filename based on the ContentType. How the issue was disovered: I implemented an otherwise unrelated application written in go that parses emails with enmime just like Forgejo does. I noticed that in a few occasions that system would fail to detect all attachments. Investigating this issue led me to realize the above described issue. After implementing a fix for that application, I looked through the Forgejo email parsing code and discovered the same problem. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7136 Reviewed-by: Gusted Co-authored-by: christopher-besch Co-committed-by: christopher-besch --- services/mailer/incoming/incoming.go | 39 ++++- services/mailer/incoming/incoming_test.go | 188 ++++++++++++++++++++++ 2 files changed, 224 insertions(+), 3 deletions(-) diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go index 092f738cd9..1b1be4c656 100644 --- a/services/mailer/incoming/incoming.go +++ b/services/mailer/incoming/incoming.go @@ -7,8 +7,10 @@ import ( "context" "crypto/tls" "fmt" + "mime" net_mail "net/mail" "regexp" + "slices" "strings" "time" @@ -374,25 +376,56 @@ type Attachment struct { // getContentFromMailReader grabs the plain content and the attachments from the mail. // A potential reply/signature gets stripped from the content. func getContentFromMailReader(env *enmime.Envelope) *MailContent { + // get attachments attachments := make([]*Attachment, 0, len(env.Attachments)) for _, attachment := range env.Attachments { attachments = append(attachments, &Attachment{ - Name: attachment.FileName, + Name: constructFilename(attachment), Content: attachment.Content, }) } + // get inlines inlineAttachments := make([]*Attachment, 0, len(env.Inlines)) for _, inline := range env.Inlines { if inline.FileName != "" && inline.ContentType != "text/plain" { inlineAttachments = append(inlineAttachments, &Attachment{ - Name: inline.FileName, + Name: constructFilename(inline), Content: inline.Content, }) } } + // get other parts (mostly multipart/related files, these are for example embedded images in an html mail) + otherParts := make([]*Attachment, 0, len(env.Inlines)) + for _, otherPart := range env.OtherParts { + otherParts = append(otherParts, &Attachment{ + Name: constructFilename(otherPart), + Content: otherPart.Content, + }) + } return &MailContent{ Content: reply.FromText(env.Text), - Attachments: append(attachments, inlineAttachments...), + Attachments: slices.Concat(attachments, inlineAttachments, otherParts), } } + +// constructFilename interprets the mime part as an (inline) attachment and returns its filename +// If no filename is given it guesses a sensible filename for it based on the filetype. +func constructFilename(part *enmime.Part) string { + if strings.TrimSpace(part.FileName) != "" { + return part.FileName + } + + filenameWOExtension := "unnamed_file" + if strings.TrimSpace(part.ContentID) != "" { + filenameWOExtension = part.ContentID + } + + fileExtension := ".unknown" + mimeExtensions, err := mime.ExtensionsByType(part.ContentType) + if err == nil && len(mimeExtensions) != 0 { + // just use the first one we find + fileExtension = mimeExtensions[0] + } + return filenameWOExtension + fileExtension +} diff --git a/services/mailer/incoming/incoming_test.go b/services/mailer/incoming/incoming_test.go index fe11c9e5c6..2ffaac57ae 100644 --- a/services/mailer/incoming/incoming_test.go +++ b/services/mailer/incoming/incoming_test.go @@ -4,6 +4,7 @@ package incoming import ( + "encoding/base64" "strings" "testing" @@ -194,4 +195,191 @@ func TestGetContentFromMailReader(t *testing.T) { require.NoError(t, err) assert.Equal(t, "mail content without signature", content.Content) assert.Empty(t, content.Attachments) + + // Some versions of Outlook send inline attachments like this, inside a multipart/related part. + // the attached image is from: https://openmoji.org/library/emoji-1F684 + mailString = "Content-Type: multipart/related; boundary=\"=_related boundary=\"\r\n" + + "\r\n" + + "This text is for clients unable to decode multipart/related with multipart/alternative.\r\n" + + "\r\n" + + "--=_related boundary=\r\n" + + "Content-Type: multipart/alternative; boundary=\"=_alternative boundary=\"\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "--=_alternative boundary=\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "This is the plaintext.\r\n" + + "\r\n" + + "--=_alternative boundary=\r\n" + + "Content-Type: text/html\r\n" + + "\r\n" + + "

This is a mail with multipart/related. Here is an image sent with a filename.

\r\n" + + "\r\n" + + "\r\n" + + "--=_alternative boundary=--\r\n" + + "\r\n" + + "--=_related boundary=\r\n" + + "Content-Transfer-Encoding: base64\r\n" + + "Content-Type: image/png;\r\n" + + " name=\"image001.png\"\r\n" + + "Content-ID: <_1_2845>\r\n" + + "\r\n" + + "iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" + + "6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" + + "LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" + + "+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" + + "7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" + + "/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" + + "19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" + + "WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" + + "rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" + + "unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" + + "GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" + + "XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" + + "gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" + + "ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" + + "AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" + + "+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" + + "KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" + + "3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" + + "Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" + + "B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" + + "5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" + + "yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" + + "7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" + + "wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" + + "exgAAAAASUVORK5CYII=\r\n" + + "\r\n" + + "--=_related boundary=--\r\n" + + "\r\n" + + env, err = enmime.ReadEnvelope(strings.NewReader(mailString)) + require.NoError(t, err) + content = getContentFromMailReader(env) + assert.Equal(t, "This is the plaintext.", content.Content) + assert.Len(t, content.Attachments, 1) + assert.Equal(t, "image001.png", content.Attachments[0].Name) + expectedAttachment, err := base64.StdEncoding.DecodeString( + "iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" + + "6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" + + "LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" + + "+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" + + "7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" + + "/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" + + "19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" + + "WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" + + "rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" + + "unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" + + "GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" + + "XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" + + "gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" + + "ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" + + "AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" + + "+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" + + "KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" + + "3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" + + "Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" + + "B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" + + "5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" + + "yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" + + "7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" + + "wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" + + "exgAAAAASUVORK5CYII=\r\n") + require.NoError(t, err) + assert.Equal(t, expectedAttachment, content.Attachments[0].Content) + + // HCL Notes inlines attachments like this: without a filename. + // the attached image is from: https://openmoji.org/library/emoji-1F684 + mailString = "Content-Type: multipart/related; boundary=\"=_related boundary=\"\r\n" + + "\r\n" + + "This text is for clients unable to decode multipart/related with multipart/alternative.\r\n" + + "\r\n" + + "--=_related boundary=\r\n" + + "Content-Type: multipart/alternative; boundary=\"=_alternative boundary=\"\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "--=_alternative boundary=\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "This is the plaintext.\r\n" + + "\r\n" + + "--=_alternative boundary=\r\n" + + "Content-Type: text/html\r\n" + + "\r\n" + + "

This is a mail with multipart/related. Here is an image sent without a filename.

\r\n" + + "\r\n" + + "\r\n" + + "--=_alternative boundary=--\r\n" + + "\r\n" + + "--=_related boundary=\r\n" + + "Content-Transfer-Encoding: base64\r\n" + + "Content-Type: image/png\r\n" + + "Content-ID: <_1_2845>\r\n" + + "\r\n" + + "iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" + + "6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" + + "LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" + + "+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" + + "7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" + + "/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" + + "19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" + + "WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" + + "rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" + + "unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" + + "GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" + + "XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" + + "gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" + + "ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" + + "AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" + + "+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" + + "KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" + + "3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" + + "Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" + + "B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" + + "5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" + + "yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" + + "7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" + + "wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" + + "exgAAAAASUVORK5CYII=\r\n" + + "\r\n" + + "--=_related boundary=--\r\n" + + "\r\n" + + env, err = enmime.ReadEnvelope(strings.NewReader(mailString)) + require.NoError(t, err) + content = getContentFromMailReader(env) + assert.Equal(t, "This is the plaintext.", content.Content) + assert.Len(t, content.Attachments, 1) + assert.Equal(t, "_1_2845.png", content.Attachments[0].Name) + expectedAttachment, err = base64.StdEncoding.DecodeString( + "iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" + + "6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" + + "LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" + + "+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" + + "7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" + + "/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" + + "19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" + + "WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" + + "rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" + + "unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" + + "GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" + + "XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" + + "gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" + + "ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" + + "AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" + + "+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" + + "KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" + + "3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" + + "Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" + + "B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" + + "5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" + + "yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" + + "7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" + + "wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" + + "exgAAAAASUVORK5CYII=\r\n") + require.NoError(t, err) + assert.Equal(t, expectedAttachment, content.Attachments[0].Content) } From 9dea54a9d6133ef52deedbfa2a43c9dc3b0509ef Mon Sep 17 00:00:00 2001 From: Otto Richter Date: Sat, 8 Mar 2025 00:43:41 +0000 Subject: [PATCH 62/79] Drop SSPI auth support and more Windows files (#7148) ## Dropping SSPI auth support SSPI authentication relied on Microsoft Windows support, removal started in https://codeberg.org/forgejo/forgejo/pulls/5353, because it was broken anyway. We have no knowledge of any users using SSPI authentication. However, if you somehow managed to run Forgejo on Windows, or want to upgrade from a Gitea version which does, please ensure that you do not use SSPI as an authentication mechanism for user accounts. Feel free to reach out if you need assistance. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7148 Reviewed-by: Gusted Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Otto Richter Co-committed-by: Otto Richter --- Makefile | 27 +-- models/asymkey/ssh_key_authorized_keys.go | 21 +- models/auth/source.go | 22 +- models/migrations/test/tests.go | 4 - modules/git/blame.go | 2 +- modules/git/command.go | 12 - modules/git/git.go | 27 +-- modules/graceful/manager_unix.go | 2 - modules/graceful/net_unix.go | 2 - modules/graceful/restart_unix.go | 2 - modules/log/color_console_other.go | 2 - modules/log/color_console_windows.go | 42 ---- modules/markup/external/external.go | 4 - modules/process/manager_unix.go | 2 - modules/repository/hooks.go | 5 - modules/setting/path.go | 6 +- modules/setting/setting.go | 8 +- modules/user/user.go | 9 +- modules/user/user_test.go | 5 - modules/util/file_unix.go | 2 - modules/util/file_unix_test.go | 2 - modules/util/path.go | 41 +--- modules/util/path_test.go | 52 +--- modules/util/remove.go | 21 -- options/locale/locale_en-US.ini | 15 +- routers/api/shared/middleware.go | 6 - routers/install/install.go | 11 +- routers/private/manager_unix.go | 2 - routers/web/admin/auths.go | 65 ----- routers/web/auth/auth.go | 2 - routers/web/web.go | 6 - services/auth/signin.go | 1 - .../auth/source/sspi/assert_interface_test.go | 18 -- services/auth/source/sspi/source.go | 39 --- services/auth/sspi.go | 223 ------------------ services/auth/sspiauth_posix.go | 30 --- services/forms/auth_form.go | 5 - templates/admin/auth/edit.tmpl | 45 ---- templates/admin/auth/new.tmpl | 3 - templates/admin/auth/source/sspi.tmpl | 43 ---- templates/user/auth/oauth_container.tmpl | 6 - tests/test_utils.go | 3 - web_src/js/features/admin/common.js | 10 +- 43 files changed, 39 insertions(+), 816 deletions(-) delete mode 100644 modules/log/color_console_windows.go delete mode 100644 services/auth/source/sspi/assert_interface_test.go delete mode 100644 services/auth/source/sspi/source.go delete mode 100644 services/auth/sspi.go delete mode 100644 services/auth/sspiauth_posix.go delete mode 100644 templates/admin/auth/source/sspi.tmpl diff --git a/Makefile b/Makefile index 4f88b2b0a5..7993d06c3d 100644 --- a/Makefile +++ b/Makefile @@ -59,20 +59,8 @@ ifeq ($(HAS_GO), yes) CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS) endif -ifeq ($(GOOS),windows) - IS_WINDOWS := yes -else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows) - ifeq ($(GOOS),) - IS_WINDOWS := yes - endif -endif -ifeq ($(IS_WINDOWS),yes) - GOFLAGS := -v -buildmode=exe - EXECUTABLE ?= gitea.exe -else - GOFLAGS := -v - EXECUTABLE ?= gitea -endif +GOFLAGS := -v +EXECUTABLE ?= gitea ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu) SED_INPLACE := sed -i @@ -498,13 +486,6 @@ lint-go-fix: $(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix $(RUN_DEADCODE) > .deadcode-out -# workaround step for the lint-go-windows CI task because 'go run' can not -# have distinct GOOS/GOARCH for its build and run steps -.PHONY: lint-go-windows -lint-go-windows: - @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) - golangci-lint run - .PHONY: lint-go-vet lint-go-vet: @echo "Running go vet..." @@ -877,10 +858,6 @@ sources-tarbal: frontend generate vendor release-sources release-check $(DIST_DIRS): mkdir -p $(DIST_DIRS) -.PHONY: release-windows -release-windows: | $(DIST_DIRS) - CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . - .PHONY: release-linux release-linux: | $(DIST_DIRS) CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out forgejo-$(VERSION) . diff --git a/models/asymkey/ssh_key_authorized_keys.go b/models/asymkey/ssh_key_authorized_keys.go index d3f9f3f3be..57baf89c0c 100644 --- a/models/asymkey/ssh_key_authorized_keys.go +++ b/models/asymkey/ssh_key_authorized_keys.go @@ -87,19 +87,16 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error { } defer f.Close() - // Note: chmod command does not support in Windows. - if !setting.IsWindows { - fi, err := f.Stat() - if err != nil { - return err - } + fi, err := f.Stat() + if err != nil { + return err + } - // .ssh directory should have mode 700, and authorized_keys file should have mode 600. - if fi.Mode().Perm() > 0o600 { - log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String()) - if err = f.Chmod(0o600); err != nil { - return err - } + // .ssh directory should have mode 700, and authorized_keys file should have mode 600. + if fi.Mode().Perm() > 0o600 { + log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String()) + if err = f.Chmod(0o600); err != nil { + return err } } diff --git a/models/auth/source.go b/models/auth/source.go index 214eb3afa0..d7ed6b97a8 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -32,7 +32,7 @@ const ( PAM // 4 DLDAP // 5 OAuth2 // 6 - SSPI // 7 + _ // 7 (was SSPI) Remote // 8 ) @@ -53,7 +53,6 @@ var Names = map[Type]string{ SMTP: "SMTP", PAM: "PAM", OAuth2: "OAuth2", - SSPI: "SPNEGO with SSPI", Remote: "Remote", } @@ -178,11 +177,6 @@ func (source *Source) IsOAuth2() bool { return source.Type == OAuth2 } -// IsSSPI returns true of this source is of the SSPI type. -func (source *Source) IsSSPI() bool { - return source.Type == SSPI -} - func (source *Source) IsRemote() bool { return source.Type == Remote } @@ -265,20 +259,6 @@ func (opts FindSourcesOptions) ToConds() builder.Cond { return conds } -// IsSSPIEnabled returns true if there is at least one activated login -// source of type LoginSSPI -func IsSSPIEnabled(ctx context.Context) bool { - exist, err := db.Exist[Source](ctx, FindSourcesOptions{ - IsActive: optional.Some(true), - LoginType: SSPI, - }.ToConds()) - if err != nil { - log.Error("IsSSPIEnabled: failed to query active SSPI sources: %v", err) - return false - } - return exist -} - // GetSourceByID returns login source by given ID. func GetSourceByID(ctx context.Context, id int64) (*Source, error) { source := new(Source) diff --git a/models/migrations/test/tests.go b/models/migrations/test/tests.go index 0e37233471..177e33c56a 100644 --- a/models/migrations/test/tests.go +++ b/models/migrations/test/tests.go @@ -11,7 +11,6 @@ import ( "os" "path" "path/filepath" - "runtime" "strings" "testing" "time" @@ -123,9 +122,6 @@ func MainTest(m *testing.M) { os.Exit(1) } giteaBinary := "gitea" - if runtime.GOOS == "windows" { - giteaBinary += ".exe" - } setting.AppPath = path.Join(giteaRoot, giteaBinary) if _, err := os.Stat(setting.AppPath); err != nil { fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath) diff --git a/modules/git/blame.go b/modules/git/blame.go index 69e1b08f93..d62a8ca0a2 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -139,7 +139,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain") if ignoreRevsFile != nil { // Possible improvement: use --ignore-revs-file /dev/stdin on unix - // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. + // This was not done in Gitea because it would not have been compatible with Windows. cmd.AddOptionValues("--ignore-revs-file", *ignoreRevsFile) } cmd.AddDynamicArguments(commit.ID.String()). diff --git a/modules/git/command.go b/modules/git/command.go index 605816b7a2..72640a2a94 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -12,7 +12,6 @@ import ( "io" "os" "os/exec" - "runtime" "runtime/trace" "strings" "time" @@ -359,17 +358,6 @@ func (c *Command) Run(opts *RunOpts) error { log.Debug("slow git.Command.Run: %s (%s)", c, elapsed) } - // We need to check if the context is canceled by the program on Windows. - // This is because Windows does not have signal checking when terminating the process. - // It always returns exit code 1, unlike Linux, which has many exit codes for signals. - if runtime.GOOS == "windows" && - err != nil && - err.Error() == "" && - cmd.ProcessState.ExitCode() == 1 && - ctx.Err() == context.Canceled { - return ctx.Err() - } - if err != nil && ctx.Err() != context.DeadlineExceeded { return err } diff --git a/modules/git/git.go b/modules/git/git.go index f1174e67b9..576609068a 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -59,15 +59,7 @@ func loadGitVersion() error { return fmt.Errorf("invalid git version output: %s", stdout) } - var versionString string - - // Handle special case on Windows. - i := strings.Index(fields[2], "windows") - if i >= 1 { - versionString = fields[2][:i-1] - } else { - versionString = fields[2] - } + versionString := fields[2] var err error gitVersion, err = version.NewVersion(versionString) @@ -280,24 +272,11 @@ func syncGitConfig() (err error) { // Thus the owner uid/gid for files on these filesystems will be marked as root. // As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea, // it is now safe to set "safe.directory=*" for internal usage only. - // Please note: the wildcard "*" is only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later - // Although only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later - this setting is tolerated by earlier versions + // Please note: the wildcard "*" is only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later, + // but is tolerated by earlier versions if err := configAddNonExist("safe.directory", "*"); err != nil { return err } - if runtime.GOOS == "windows" { - if err := configSet("core.longpaths", "true"); err != nil { - return err - } - if setting.Git.DisableCoreProtectNTFS { - err = configSet("core.protectNTFS", "false") - } else { - err = configUnsetAll("core.protectNTFS", "false") - } - if err != nil { - return err - } - } // By default partial clones are disabled, enable them from git v2.22 if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index 931b0f1b62..8147743f79 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -1,8 +1,6 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package graceful import ( diff --git a/modules/graceful/net_unix.go b/modules/graceful/net_unix.go index 796e00507c..97b591f98f 100644 --- a/modules/graceful/net_unix.go +++ b/modules/graceful/net_unix.go @@ -3,8 +3,6 @@ // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler -//go:build !windows - package graceful import ( diff --git a/modules/graceful/restart_unix.go b/modules/graceful/restart_unix.go index 98d5c5cc20..a0f3147ec6 100644 --- a/modules/graceful/restart_unix.go +++ b/modules/graceful/restart_unix.go @@ -3,8 +3,6 @@ // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler -//go:build !windows - package graceful import ( diff --git a/modules/log/color_console_other.go b/modules/log/color_console_other.go index c08b38c674..6573d093a5 100644 --- a/modules/log/color_console_other.go +++ b/modules/log/color_console_other.go @@ -1,8 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package log import ( diff --git a/modules/log/color_console_windows.go b/modules/log/color_console_windows.go deleted file mode 100644 index 3f59e934da..0000000000 --- a/modules/log/color_console_windows.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import ( - "os" - - "github.com/mattn/go-isatty" - "golang.org/x/sys/windows" -) - -func enableVTMode(console windows.Handle) bool { - mode := uint32(0) - err := windows.GetConsoleMode(console, &mode) - if err != nil { - return false - } - - // EnableVirtualTerminalProcessing is the console mode to allow ANSI code - // interpretation on the console. See: - // https://docs.microsoft.com/en-us/windows/console/setconsolemode - // It only works on Windows 10. Earlier terminals will fail with an err which we will - // handle to say don't color - mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING - err = windows.SetConsoleMode(console, mode) - return err == nil -} - -func init() { - if isatty.IsTerminal(os.Stdout.Fd()) { - CanColorStdout = enableVTMode(windows.Stdout) - } else { - CanColorStdout = isatty.IsCygwinTerminal(os.Stderr.Fd()) - } - - if isatty.IsTerminal(os.Stderr.Fd()) { - CanColorStderr = enableVTMode(windows.Stderr) - } else { - CanColorStderr = isatty.IsCygwinTerminal(os.Stderr.Fd()) - } -} diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index 122517ed11..59f0397515 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -9,7 +9,6 @@ import ( "io" "os" "os/exec" - "runtime" "strings" "code.gitea.io/gitea/modules/graceful" @@ -70,9 +69,6 @@ func (p *Renderer) DisplayInIFrame() bool { } func envMark(envName string) string { - if runtime.GOOS == "windows" { - return "%" + envName + "%" - } return "$" + envName } diff --git a/modules/process/manager_unix.go b/modules/process/manager_unix.go index c5be906b35..54dd6dc485 100644 --- a/modules/process/manager_unix.go +++ b/modules/process/manager_unix.go @@ -1,8 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package process import ( diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go index 95849789ab..75a21a09dd 100644 --- a/modules/repository/hooks.go +++ b/modules/repository/hooks.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "path/filepath" - "runtime" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -146,10 +145,6 @@ func CreateDelegateHooks(repoPath string) (err error) { } func checkExecutable(filename string) bool { - // windows has no concept of a executable bit - if runtime.GOOS == "windows" { - return true - } fileInfo, err := os.Stat(filename) if err != nil { return false diff --git a/modules/setting/path.go b/modules/setting/path.go index 85d0e06302..b99f1977bb 100644 --- a/modules/setting/path.go +++ b/modules/setting/path.go @@ -34,11 +34,7 @@ var ( func getAppPath() (string, error) { var appPath string var err error - if IsWindows && filepath.IsAbs(os.Args[0]) { - appPath = filepath.Clean(os.Args[0]) - } else { - appPath, err = exec.LookPath(os.Args[0]) - } + appPath, err = exec.LookPath(os.Args[0]) if err != nil { if !errors.Is(err, exec.ErrDot) { return "", err diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 8350b914c5..487f2bb0d5 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -8,7 +8,6 @@ package setting import ( "fmt" "os" - "runtime" "strings" "time" @@ -34,7 +33,6 @@ var ( RunMode string RunUser string IsProd bool - IsWindows bool // IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing // TODO: this is only a temporary solution, we should make the test code more reliable @@ -42,22 +40,18 @@ var ( ) func init() { - IsWindows = runtime.GOOS == "windows" if AppVer == "" { AppVer = "dev" } - // We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically // By default set this logger at Info - we'll change it later, but we need to start with something. log.SetConsoleLogger(log.DEFAULT, "console", log.INFO) } // IsRunUserMatchCurrentUser returns false if configured run user does not match // actual user that runs the app. The first return value is the actual user name. -// This check is ignored under Windows since SSH remote login is not the main -// method to login on Windows. func IsRunUserMatchCurrentUser(runUser string) (string, bool) { - if IsWindows || SSH.StartBuiltinServer { + if SSH.StartBuiltinServer { return "", true } diff --git a/modules/user/user.go b/modules/user/user.go index eee401a23f..d153413c70 100644 --- a/modules/user/user.go +++ b/modules/user/user.go @@ -6,8 +6,6 @@ package user import ( "os" "os/user" - "runtime" - "strings" ) // CurrentUsername return current login OS user name @@ -16,12 +14,7 @@ func CurrentUsername() string { if err != nil { return fallbackCurrentUsername() } - username := userinfo.Username - if runtime.GOOS == "windows" { - parts := strings.Split(username, "\\") - username = parts[len(parts)-1] - } - return username + return userinfo.Username } // Old method, used if new method doesn't work on your OS for some reason diff --git a/modules/user/user_test.go b/modules/user/user_test.go index 372a675d34..c7eff85c90 100644 --- a/modules/user/user_test.go +++ b/modules/user/user_test.go @@ -5,7 +5,6 @@ package user import ( "os/exec" - "runtime" "strings" "testing" ) @@ -23,10 +22,6 @@ func TestCurrentUsername(t *testing.T) { if len(user) == 0 { t.Errorf("expected non-empty user, got: %s", user) } - // Windows whoami is weird, so just skip remaining tests - if runtime.GOOS == "windows" { - t.Skip("skipped test because of weird whoami on Windows") - } whoami, err := getWhoamiOutput() if err != nil { t.Errorf("failed to run whoami to test current user: %f", err) diff --git a/modules/util/file_unix.go b/modules/util/file_unix.go index 79a29c8b3b..b722eee97d 100644 --- a/modules/util/file_unix.go +++ b/modules/util/file_unix.go @@ -1,8 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package util import ( diff --git a/modules/util/file_unix_test.go b/modules/util/file_unix_test.go index d60082a034..228c64f980 100644 --- a/modules/util/file_unix_test.go +++ b/modules/util/file_unix_test.go @@ -1,8 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package util import ( diff --git a/modules/util/path.go b/modules/util/path.go index 185e7cf882..9039f27cbf 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -10,8 +10,6 @@ import ( "os" "path" "path/filepath" - "regexp" - "runtime" "strings" ) @@ -78,11 +76,7 @@ func FilePathJoinAbs(base string, sub ...string) string { // POSIX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators // to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/` - if isOSWindows() { - elems[0] = filepath.Clean(base) - } else { - elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator)) - } + elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator)) if !filepath.IsAbs(elems[0]) { // This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", elems[0], elems)) @@ -91,11 +85,7 @@ func FilePathJoinAbs(base string, sub ...string) string { if s == "" { continue } - if isOSWindows() { - elems = append(elems, filepath.Clean(pathSeparator+s)) - } else { - elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator))) - } + elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator))) } // the elems[0] must be an absolute path, just join them together return filepath.Join(elems...) @@ -217,12 +207,6 @@ func StatDir(rootPath string, includeDir ...bool) ([]string, error) { return statDir(rootPath, "", isIncludeDir, false, false) } -func isOSWindows() bool { - return runtime.GOOS == "windows" -} - -var driveLetterRegexp = regexp.MustCompile("/[A-Za-z]:/") - // FileURLToPath extracts the path information from a file://... url. // It returns an error only if the URL is not a file URL. func FileURLToPath(u *url.URL) (string, error) { @@ -230,17 +214,7 @@ func FileURLToPath(u *url.URL) (string, error) { return "", errors.New("URL scheme is not 'file': " + u.String()) } - path := u.Path - - if !isOSWindows() { - return path, nil - } - - // If it looks like there's a Windows drive letter at the beginning, strip off the leading slash. - if driveLetterRegexp.MatchString(path) { - return path[1:], nil - } - return path, nil + return u.Path, nil } // HomeDir returns path of '~'(in Linux) on Windows, @@ -249,14 +223,7 @@ func HomeDir() (home string, err error) { // TODO: some users run Gitea with mismatched uid and "HOME=xxx" (they set HOME=xxx by environment manually) // TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory // so at the moment we can not use `user.Current().HomeDir` - if isOSWindows() { - home = os.Getenv("USERPROFILE") - if home == "" { - home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") - } - } else { - home = os.Getenv("HOME") - } + home = os.Getenv("HOME") if home == "" { return "", errors.New("cannot get home directory") diff --git a/modules/util/path_test.go b/modules/util/path_test.go index 3699f052d1..b912b76f6e 100644 --- a/modules/util/path_test.go +++ b/modules/util/path_test.go @@ -5,7 +5,6 @@ package util import ( "net/url" - "runtime" "testing" "github.com/stretchr/testify/assert" @@ -17,7 +16,6 @@ func TestFileURLToPath(t *testing.T) { url string expected string haserror bool - windows bool }{ // case 0 { @@ -34,18 +32,9 @@ func TestFileURLToPath(t *testing.T) { url: "file:///path", expected: "/path", }, - // case 3 - { - url: "file:///C:/path", - expected: "C:/path", - windows: true, - }, } for n, c := range cases { - if c.windows && runtime.GOOS != "windows" { - continue - } u, _ := url.Parse(c.url) p, err := FileURLToPath(u) if c.haserror { @@ -177,35 +166,18 @@ func TestCleanPath(t *testing.T) { assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems) } - // for POSIX only, but the result is similar on Windows, because the first element must be an absolute path - if isOSWindows() { - cases = []struct { - elems []string - expected string - }{ - {[]string{`C:\..`}, `C:\`}, - {[]string{`C:\a`}, `C:\a`}, - {[]string{`C:\a/`}, `C:\a`}, - {[]string{`C:\..\a\`, `../b`, `c\..`, `d`}, `C:\a\b\d`}, - {[]string{`C:\a/..\b`}, `C:\b`}, - {[]string{`C:\a`, ``, `b`}, `C:\a\b`}, - {[]string{`C:\a`, `..`, `b`}, `C:\a\b`}, - {[]string{`C:\lfs`, `repo/..`, `user/../path`}, `C:\lfs\path`}, - } - } else { - cases = []struct { - elems []string - expected string - }{ - {[]string{`/..`}, `/`}, - {[]string{`/a`}, `/a`}, - {[]string{`/a/`}, `/a`}, - {[]string{`/../a/`, `../b`, `c/..`, `d`}, `/a/b/d`}, - {[]string{`/a\..\b`}, `/b`}, - {[]string{`/a`, ``, `b`}, `/a/b`}, - {[]string{`/a`, `..`, `b`}, `/a/b`}, - {[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`}, - } + cases = []struct { + elems []string + expected string + }{ + {[]string{`/..`}, `/`}, + {[]string{`/a`}, `/a`}, + {[]string{`/a/`}, `/a`}, + {[]string{`/../a/`, `../b`, `c/..`, `d`}, `/a/b/d`}, + {[]string{`/a\..\b`}, `/b`}, + {[]string{`/a`, ``, `b`}, `/a/b`}, + {[]string{`/a`, `..`, `b`}, `/a/b`}, + {[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`}, } for _, c := range cases { assert.Equal(t, c.expected, FilePathJoinAbs(c.elems[0], c.elems[1:]...), "case: %v", c.elems) diff --git a/modules/util/remove.go b/modules/util/remove.go index d1e38faf5f..2a65a6b0aa 100644 --- a/modules/util/remove.go +++ b/modules/util/remove.go @@ -5,13 +5,10 @@ package util import ( "os" - "runtime" "syscall" "time" ) -const windowsSharingViolationError syscall.Errno = 32 - // Remove removes the named file or (empty) directory with at most 5 attempts. func Remove(name string) error { var err error @@ -27,12 +24,6 @@ func Remove(name string) error { continue } - if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" { - // try again - <-time.After(100 * time.Millisecond) - continue - } - if unwrapped == syscall.ENOENT { // it's already gone return nil @@ -56,12 +47,6 @@ func RemoveAll(name string) error { continue } - if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" { - // try again - <-time.After(100 * time.Millisecond) - continue - } - if unwrapped == syscall.ENOENT { // it's already gone return nil @@ -85,12 +70,6 @@ func Rename(oldpath, newpath string) error { continue } - if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" { - // try again - <-time.After(100 * time.Millisecond) - continue - } - if i == 0 && os.IsNotExist(err) { return err } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index af7142e7e5..8459d72f9e 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -609,9 +609,6 @@ CommitChoice = Commit choice TreeName = File path Content = Content -SSPISeparatorReplacement = Separator -SSPIDefaultLanguage = Default language - require_error = ` cannot be empty.` alpha_dash_error = ` should contain only alphanumeric, dash ("-") and underscore ("_") characters.` alpha_dash_dot_error = ` should contain only alphanumeric, dash ("-"), underscore ("_") and dot (".") characters.` @@ -3300,16 +3297,6 @@ auths.oauth2_admin_group = Group claim value for administrator users. (Optional auths.oauth2_restricted_group = Group claim value for restricted users. (Optional - requires claim name above) auths.oauth2_map_group_to_team = Map claimed groups to organization teams. (Optional - requires claim name above) auths.oauth2_map_group_to_team_removal = Remove users from synchronized teams if user does not belong to corresponding group. -auths.sspi_auto_create_users = Automatically create users -auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time -auths.sspi_auto_activate_users = Automatically activate users -auths.sspi_auto_activate_users_helper = Allow SSPI auth method to automatically activate new users -auths.sspi_strip_domain_names = Remove domain names from usernames -auths.sspi_strip_domain_names_helper = If checked, domain names will be removed from logon names (eg. "DOMAIN\user" and "user@example.org" both will become just "user"). -auths.sspi_separator_replacement = Separator to use instead of \, / and @ -auths.sspi_separator_replacement_helper = The character to use to replace the separators of down-level logon names (eg. the \ in "DOMAIN\user") and user principal names (eg. the @ in "user@example.org"). -auths.sspi_default_language = Default user language -auths.sspi_default_language_helper = Default language for users automatically created by SSPI auth method. Leave empty if you prefer language to be automatically detected. auths.tips = Tips auths.tips.gmail_settings = Gmail settings: auths.tips.oauth2.general = OAuth2 authentication @@ -3965,4 +3952,4 @@ filepreview.lines = Lines %[1]d to %[2]d in %[3]s filepreview.truncated = Preview has been truncated [translation_meta] -test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :) \ No newline at end of file +test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :) diff --git a/routers/api/shared/middleware.go b/routers/api/shared/middleware.go index e2ff004024..5e863cb9eb 100644 --- a/routers/api/shared/middleware.go +++ b/routers/api/shared/middleware.go @@ -6,8 +6,6 @@ package shared import ( "net/http" - auth_model "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/common" @@ -51,10 +49,6 @@ func buildAuthGroup() *auth.Group { group.Add(&auth.ReverseProxy{}) } - if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) { - group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI - } - return group } diff --git a/routers/install/install.go b/routers/install/install.go index 24db25f459..86e342f1f9 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -29,7 +29,6 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" - "code.gitea.io/gitea/modules/user" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers/common" @@ -119,15 +118,7 @@ func Install(ctx *context.Context) { form.AppSlogan = "Beyond coding. We Forge." form.RepoRootPath = setting.RepoRootPath form.LFSRootPath = setting.LFS.Storage.Path - - // Note(unknown): it's hard for Windows users change a running user, - // so just use current one if config says default. - if setting.IsWindows && setting.RunUser == "git" { - form.RunUser = user.CurrentUsername() - } else { - form.RunUser = setting.RunUser - } - + form.RunUser = setting.RunUser form.Domain = setting.Domain form.SSHPort = setting.SSH.Port form.HTTPPort = setting.HTTPPort diff --git a/routers/private/manager_unix.go b/routers/private/manager_unix.go index 0c63ebc918..311bfe6858 100644 --- a/routers/private/manager_unix.go +++ b/routers/private/manager_unix.go @@ -1,8 +1,6 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !windows - package private import ( diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index dcdc8e6a2a..8af14f6d52 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -4,11 +4,9 @@ package admin import ( - "errors" "fmt" "net/http" "net/url" - "regexp" "strconv" "strings" @@ -18,14 +16,12 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/oauth2" pam_service "code.gitea.io/gitea/services/auth/source/pam" "code.gitea.io/gitea/services/auth/source/smtp" - "code.gitea.io/gitea/services/auth/source/sspi" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" @@ -38,11 +34,6 @@ const ( tplAuthEdit base.TplName = "admin/auth/edit" ) -var ( - separatorAntiPattern = regexp.MustCompile(`[^\w-\.]`) - langCodePattern = regexp.MustCompile(`^[a-z]{2}-[A-Z]{2}$`) -) - // Authentications show authentication config page func Authentications(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.authentication") @@ -70,7 +61,6 @@ var ( {auth.DLDAP.String(), auth.DLDAP}, {auth.SMTP.String(), auth.SMTP}, {auth.OAuth2.String(), auth.OAuth2}, - {auth.SSPI.String(), auth.SSPI}, } if pam.Supported { items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM}) @@ -102,12 +92,6 @@ func NewAuthSource(ctx *context.Context) { oauth2providers := oauth2.GetSupportedOAuth2Providers() ctx.Data["OAuth2Providers"] = oauth2providers - ctx.Data["SSPIAutoCreateUsers"] = true - ctx.Data["SSPIAutoActivateUsers"] = true - ctx.Data["SSPIStripDomainNames"] = true - ctx.Data["SSPISeparatorReplacement"] = "_" - ctx.Data["SSPIDefaultLanguage"] = "" - // only the first as default ctx.Data["oauth2_provider"] = oauth2providers[0].Name() @@ -209,30 +193,6 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { } } -func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) { - if util.IsEmptyString(form.SSPISeparatorReplacement) { - ctx.Data["Err_SSPISeparatorReplacement"] = true - return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.require_error")) - } - if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) { - ctx.Data["Err_SSPISeparatorReplacement"] = true - return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.alpha_dash_dot_error")) - } - - if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) { - ctx.Data["Err_SSPIDefaultLanguage"] = true - return nil, errors.New(ctx.Locale.TrString("form.lang_select_error")) - } - - return &sspi.Source{ - AutoCreateUsers: form.SSPIAutoCreateUsers, - AutoActivateUsers: form.SSPIAutoActivateUsers, - StripDomainNames: form.SSPIStripDomainNames, - SeparatorReplacement: form.SSPISeparatorReplacement, - DefaultLanguage: form.SSPIDefaultLanguage, - }, nil -} - // NewAuthSourcePost response for adding an auth source func NewAuthSourcePost(ctx *context.Context) { form := *web.GetForm(ctx).(*forms.AuthenticationForm) @@ -247,12 +207,6 @@ func NewAuthSourcePost(ctx *context.Context) { oauth2providers := oauth2.GetSupportedOAuth2Providers() ctx.Data["OAuth2Providers"] = oauth2providers - ctx.Data["SSPIAutoCreateUsers"] = true - ctx.Data["SSPIAutoActivateUsers"] = true - ctx.Data["SSPIStripDomainNames"] = true - ctx.Data["SSPISeparatorReplacement"] = "_" - ctx.Data["SSPIDefaultLanguage"] = "" - hasTLS := false var config convert.Conversion switch auth.Type(form.Type) { @@ -279,19 +233,6 @@ func NewAuthSourcePost(ctx *context.Context) { return } } - case auth.SSPI: - var err error - config, err = parseSSPIConfig(ctx, form) - if err != nil { - ctx.RenderWithErr(err.Error(), tplAuthNew, form) - return - } - existing, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{LoginType: auth.SSPI}) - if err != nil || len(existing) > 0 { - ctx.Data["Err_Type"] = true - ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form) - return - } default: ctx.Error(http.StatusBadRequest) return @@ -408,12 +349,6 @@ func EditAuthSourcePost(ctx *context.Context) { return } } - case auth.SSPI: - config, err = parseSSPIConfig(ctx, form) - if err != nil { - ctx.RenderWithErr(err.Error(), tplAuthEdit, form) - return - } default: ctx.Error(http.StatusBadRequest) return diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index d592dff077..1d00c97b6e 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -164,7 +164,6 @@ func SignIn(ctx *context.Context) { ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsLogin"] = true - ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin { @@ -190,7 +189,6 @@ func SignInPost(ctx *context.Context) { ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsLogin"] = true - ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn ctx.Data["DisablePassword"] = !setting.Service.EnableInternalSignIn diff --git a/routers/web/web.go b/routers/web/web.go index a519ff74c1..128d072741 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -8,8 +8,6 @@ import ( "net/http" "strings" - auth_model "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" quota_model "code.gitea.io/gitea/models/quota" "code.gitea.io/gitea/models/unit" @@ -110,10 +108,6 @@ func buildAuthGroup() *auth_service.Group { } group.Add(&auth_service.Session{}) - if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) { - group.Add(&auth_service.SSPI{}) // it MUST be the last, see the comment of SSPI - } - return group } diff --git a/services/auth/signin.go b/services/auth/signin.go index e116a088e0..7c69da8f94 100644 --- a/services/auth/signin.go +++ b/services/auth/signin.go @@ -18,7 +18,6 @@ import ( _ "code.gitea.io/gitea/services/auth/source/db" // register the sources (and below) _ "code.gitea.io/gitea/services/auth/source/ldap" // register the ldap source _ "code.gitea.io/gitea/services/auth/source/pam" // register the pam source - _ "code.gitea.io/gitea/services/auth/source/sspi" // register the sspi source ) // UserSignIn validates user name and password. diff --git a/services/auth/source/sspi/assert_interface_test.go b/services/auth/source/sspi/assert_interface_test.go deleted file mode 100644 index 03d836dd6f..0000000000 --- a/services/auth/source/sspi/assert_interface_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package sspi_test - -import ( - "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/services/auth/source/sspi" -) - -// This test file exists to assert that our Source exposes the interfaces that we expect -// It tightly binds the interfaces and implementation without breaking go import cycles - -type sourceInterface interface { - auth.Config -} - -var _ (sourceInterface) = &sspi.Source{} diff --git a/services/auth/source/sspi/source.go b/services/auth/source/sspi/source.go deleted file mode 100644 index bdd6ef451c..0000000000 --- a/services/auth/source/sspi/source.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package sspi - -import ( - "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/modules/json" -) - -// _________ ___________________.___ -// / _____// _____/\______ \ | -// \_____ \ \_____ \ | ___/ | -// / \/ \ | | | | -// /_______ /_______ / |____| |___| -// \/ \/ - -// Source holds configuration for SSPI single sign-on. -type Source struct { - AutoCreateUsers bool - AutoActivateUsers bool - StripDomainNames bool - SeparatorReplacement string - DefaultLanguage string -} - -// FromDB fills up an SSPIConfig from serialized format. -func (cfg *Source) FromDB(bs []byte) error { - return json.UnmarshalHandleDoubleEncode(bs, &cfg) -} - -// ToDB exports an SSPIConfig to a serialized format. -func (cfg *Source) ToDB() ([]byte, error) { - return json.Marshal(cfg) -} - -func init() { - auth.RegisterTypeConfig(auth.SSPI, &Source{}) -} diff --git a/services/auth/sspi.go b/services/auth/sspi.go deleted file mode 100644 index 64a127e97a..0000000000 --- a/services/auth/sspi.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package auth - -import ( - "context" - "errors" - "net/http" - "strings" - "sync" - - "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/models/db" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/optional" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/services/auth/source/sspi" - gitea_context "code.gitea.io/gitea/services/context" - - gouuid "github.com/google/uuid" -) - -const ( - tplSignIn base.TplName = "user/auth/signin" -) - -type SSPIAuth interface { - AppendAuthenticateHeader(w http.ResponseWriter, data string) - Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error) -} - -var ( - sspiAuth SSPIAuth // a global instance of the websspi authenticator to avoid acquiring the server credential handle on every request - sspiAuthOnce sync.Once - sspiAuthErrInit error - - // Ensure the struct implements the interface. - _ Method = &SSPI{} -) - -// SSPI implements the SingleSignOn interface and authenticates requests -// via the built-in SSPI module in Windows for SPNEGO authentication. -// The SSPI plugin is expected to be executed last, as it returns 401 status code if negotiation -// fails (or if negotiation should continue), which would prevent other authentication methods -// to execute at all. -type SSPI struct{} - -// Name represents the name of auth method -func (s *SSPI) Name() string { - return "sspi" -} - -// Verify uses SSPI (Windows implementation of SPNEGO) to authenticate the request. -// If authentication is successful, returns the corresponding user object. -// If negotiation should continue or authentication fails, immediately returns a 401 HTTP -// response code, as required by the SPNEGO protocol. -func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { - sspiAuthOnce.Do(func() { sspiAuthErrInit = sspiAuthInit() }) - if sspiAuthErrInit != nil { - return nil, sspiAuthErrInit - } - if !s.shouldAuthenticate(req) { - return nil, nil - } - - cfg, err := s.getConfig(req.Context()) - if err != nil { - log.Error("could not get SSPI config: %v", err) - return nil, err - } - - log.Trace("SSPI Authorization: Attempting to authenticate") - userInfo, outToken, err := sspiAuth.Authenticate(req, w) - if err != nil { - log.Warn("Authentication failed with error: %v\n", err) - sspiAuth.AppendAuthenticateHeader(w, outToken) - - // Include the user login page in the 401 response to allow the user - // to login with another authentication method if SSPI authentication - // fails - store.GetData()["Flash"] = map[string]string{ - "ErrorMsg": err.Error(), - } - store.GetData()["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn - store.GetData()["EnableSSPI"] = true - // in this case, the Verify function is called in Gitea's web context - // FIXME: it doesn't look good to render the page here, why not redirect? - gitea_context.GetWebContext(req).HTML(http.StatusUnauthorized, tplSignIn) - return nil, err - } - if outToken != "" { - sspiAuth.AppendAuthenticateHeader(w, outToken) - } - - username := sanitizeUsername(userInfo.Username, cfg) - if len(username) == 0 { - return nil, nil - } - log.Info("Authenticated as %s\n", username) - - user, err := user_model.GetUserByName(req.Context(), username) - if err != nil { - if !user_model.IsErrUserNotExist(err) { - log.Error("GetUserByName: %v", err) - return nil, err - } - if !cfg.AutoCreateUsers { - log.Error("User '%s' not found", username) - return nil, nil - } - user, err = s.newUser(req.Context(), username, cfg) - if err != nil { - log.Error("CreateUser: %v", err) - return nil, err - } - } - - // Make sure requests to API paths and PWA resources do not create a new session - if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) { - handleSignIn(w, req, sess, user) - } - - log.Trace("SSPI Authorization: Logged in user %-v", user) - return user, nil -} - -// getConfig retrieves the SSPI configuration from login sources -func (s *SSPI) getConfig(ctx context.Context) (*sspi.Source, error) { - sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{ - IsActive: optional.Some(true), - LoginType: auth.SSPI, - }) - if err != nil { - return nil, err - } - if len(sources) == 0 { - return nil, errors.New("no active login sources of type SSPI found") - } - if len(sources) > 1 { - return nil, errors.New("more than one active login source of type SSPI found") - } - return sources[0].Cfg.(*sspi.Source), nil -} - -func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) { - shouldAuth = false - path := strings.TrimSuffix(req.URL.Path, "/") - if path == "/user/login" { - if req.FormValue("user_name") != "" && req.FormValue("password") != "" { - shouldAuth = false - } else if req.FormValue("auth_with_sspi") == "1" { - shouldAuth = true - } - } else if middleware.IsAPIPath(req) || isAttachmentDownload(req) { - shouldAuth = true - } - return shouldAuth -} - -// newUser creates a new user object for the purpose of automatic registration -// and populates its name and email with the information present in request headers. -func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (*user_model.User, error) { - email := gouuid.New().String() + "@localhost.localdomain" - user := &user_model.User{ - Name: username, - Email: email, - Language: cfg.DefaultLanguage, - } - emailNotificationPreference := user_model.EmailNotificationsDisabled - overwriteDefault := &user_model.CreateUserOverwriteOptions{ - IsActive: optional.Some(cfg.AutoActivateUsers), - KeepEmailPrivate: optional.Some(true), - EmailNotificationsPreference: &emailNotificationPreference, - } - if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil { - return nil, err - } - - return user, nil -} - -// stripDomainNames removes NETBIOS domain name and separator from down-level logon names -// (eg. "DOMAIN\user" becomes "user"), and removes the UPN suffix (domain name) and separator -// from UPNs (eg. "user@domain.local" becomes "user") -func stripDomainNames(username string) string { - if strings.Contains(username, "\\") { - parts := strings.SplitN(username, "\\", 2) - if len(parts) > 1 { - username = parts[1] - } - } else if strings.Contains(username, "@") { - parts := strings.Split(username, "@") - if len(parts) > 1 { - username = parts[0] - } - } - return username -} - -func replaceSeparators(username string, cfg *sspi.Source) string { - newSep := cfg.SeparatorReplacement - username = strings.ReplaceAll(username, "\\", newSep) - username = strings.ReplaceAll(username, "/", newSep) - username = strings.ReplaceAll(username, "@", newSep) - return username -} - -func sanitizeUsername(username string, cfg *sspi.Source) string { - if len(username) == 0 { - return "" - } - if cfg.StripDomainNames { - username = stripDomainNames(username) - } - // Replace separators even if we have already stripped the domain name part, - // as the username can contain several separators: eg. "MICROSOFT\useremail@live.com" - username = replaceSeparators(username, cfg) - return username -} diff --git a/services/auth/sspiauth_posix.go b/services/auth/sspiauth_posix.go deleted file mode 100644 index 49b0ed4a52..0000000000 --- a/services/auth/sspiauth_posix.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build !windows - -package auth - -import ( - "errors" - "net/http" -) - -type SSPIUserInfo struct { - Username string // Name of user, usually in the form DOMAIN\User - Groups []string // The global groups the user is a member of -} - -type sspiAuthMock struct{} - -func (s sspiAuthMock) AppendAuthenticateHeader(w http.ResponseWriter, data string) { -} - -func (s sspiAuthMock) Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error) { - return nil, "", errors.New("not implemented") -} - -func sspiAuthInit() error { - sspiAuth = &sspiAuthMock{} // TODO: we can mock the SSPI auth in tests - return nil -} diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 39aae51756..21443ff6a5 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -77,11 +77,6 @@ type AuthenticationForm struct { Oauth2GroupTeamMapRemoval bool Oauth2AttributeSSHPublicKey string SkipLocalTwoFA bool - SSPIAutoCreateUsers bool - SSPIAutoActivateUsers bool - SSPIStripDomainNames bool - SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"` - SSPIDefaultLanguage string GroupTeamMap string `binding:"ValidGroupTeamMap"` GroupTeamMapRemoval bool } diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 34d52ed224..1ca5573cae 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -380,51 +380,6 @@ {{end}} - - {{if .Source.IsSSPI}} - {{$cfg:=.Source.Cfg}} -
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users_helper"}}

-
-
-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_auto_activate_users_helper"}}

-
-
-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_strip_domain_names_helper"}}

-
-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_separator_replacement_helper"}}

-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_default_language_helper"}}

-
- {{end}} {{if .Source.IsLDAP}}
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl index c70bd3ba43..12d3798278 100644 --- a/templates/admin/auth/new.tmpl +++ b/templates/admin/auth/new.tmpl @@ -50,9 +50,6 @@ {{template "admin/auth/source/oauth" .}} - - {{template "admin/auth/source/sspi" .}} -
diff --git a/templates/admin/auth/source/sspi.tmpl b/templates/admin/auth/source/sspi.tmpl deleted file mode 100644 index 6a3f00f9a8..0000000000 --- a/templates/admin/auth/source/sspi.tmpl +++ /dev/null @@ -1,43 +0,0 @@ -
-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users_helper"}}

-
-
-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_auto_activate_users_helper"}}

-
-
-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_strip_domain_names_helper"}}

-
-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_separator_replacement_helper"}}

-
-
- - -

{{ctx.Locale.Tr "admin.auths.sspi_default_language_helper"}}

-
-
diff --git a/templates/user/auth/oauth_container.tmpl b/templates/user/auth/oauth_container.tmpl index c20273be14..7531320394 100644 --- a/templates/user/auth/oauth_container.tmpl +++ b/templates/user/auth/oauth_container.tmpl @@ -19,12 +19,6 @@ {{ctx.Locale.Tr "auth.sign_in_openid"}} {{end}} - {{if .EnableSSPI}} - - {{svg "fontawesome-windows"}} -  SSPI - - {{end}}
diff --git a/tests/test_utils.go b/tests/test_utils.go index 106e74dc89..c4d87ca619 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -66,9 +66,6 @@ func InitTest(requireGitea bool) { setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") if requireGitea { giteaBinary := "gitea" - if setting.IsWindows { - giteaBinary += ".exe" - } setting.AppPath = path.Join(giteaRoot, giteaBinary) if _, err := os.Stat(setting.AppPath); err != nil { exitf("Could not find gitea binary at %s", setting.AppPath) diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js index a42d8261f1..9934c3dd17 100644 --- a/web_src/js/features/admin/common.js +++ b/web_src/js/features/admin/common.js @@ -123,9 +123,9 @@ export function initAdminCommon() { // New authentication if (document.querySelector('.admin.new.authentication')) { document.getElementById('auth_type')?.addEventListener('change', function () { - hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi'); + hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size'); - for (const input of document.querySelectorAll('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]')) { + for (const input of document.querySelectorAll('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required]')) { input.removeAttribute('required'); } @@ -166,12 +166,6 @@ export function initAdminCommon() { } onOAuth2Change(true); break; - case '7': // SSPI - showElem('.sspi'); - for (const input of document.querySelectorAll('.sspi div.required input')) { - input.setAttribute('required', 'required'); - } - break; } if (authType === '2' || authType === '5') { onSecurityProtocolChange(); From 30982b9e7b625db984ced9aa7a65ca9cf1d17045 Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 8 Mar 2025 10:42:36 +0000 Subject: [PATCH 63/79] feat(auth): add ability to regenerate access tokens (#6963) - Add the ability to regenerate existing access tokens in the UI. This preserves the ID of the access token, but generates a new salt and token contents. - Integration test added. - Unit test added. - Resolves #6880 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6963 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Reviewed-by: Gusted Co-authored-by: Dmitrii Sharshakov Co-committed-by: Dmitrii Sharshakov --- models/auth/access_token.go | 34 +++++++++++- models/auth/access_token_test.go | 25 +++++++++ options/locale/locale_en-US.ini | 4 ++ routers/web/user/setting/applications.go | 18 +++++++ routers/web/web.go | 1 + templates/user/settings/applications.tmpl | 15 ++++++ tests/integration/integration_test.go | 22 ++++++-- tests/integration/user_test.go | 64 +++++++++++++++++++++++ 8 files changed, 176 insertions(+), 7 deletions(-) diff --git a/models/auth/access_token.go b/models/auth/access_token.go index 63331b4841..3ac18940a8 100644 --- a/models/auth/access_token.go +++ b/models/auth/access_token.go @@ -98,6 +98,15 @@ func init() { // NewAccessToken creates new access token. func NewAccessToken(ctx context.Context, t *AccessToken) error { + err := generateAccessToken(t) + if err != nil { + return err + } + _, err = db.GetEngine(ctx).Insert(t) + return err +} + +func generateAccessToken(t *AccessToken) error { salt, err := util.CryptoRandomString(10) if err != nil { return err @@ -110,8 +119,7 @@ func NewAccessToken(ctx context.Context, t *AccessToken) error { t.Token = hex.EncodeToString(token) t.TokenHash = HashToken(t.Token, t.TokenSalt) t.TokenLastEight = t.Token[len(t.Token)-8:] - _, err = db.GetEngine(ctx).Insert(t) - return err + return nil } // DisplayPublicOnly whether to display this as a public-only token. @@ -234,3 +242,25 @@ func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error { } return nil } + +// RegenerateAccessTokenByID regenerates access token by given ID. +// It regenerates token and salt, as well as updates the creation time. +func RegenerateAccessTokenByID(ctx context.Context, id, userID int64) (*AccessToken, error) { + t := &AccessToken{} + found, err := db.GetEngine(ctx).Where("id = ? AND uid = ?", id, userID).Get(t) + if err != nil { + return nil, err + } else if !found { + return nil, ErrAccessTokenNotExist{} + } + + err = generateAccessToken(t) + if err != nil { + return nil, err + } + + // Reset the creation time, token is unused + t.UpdatedUnix = timeutil.TimeStampNow() + + return t, UpdateAccessToken(ctx, t) +} diff --git a/models/auth/access_token_test.go b/models/auth/access_token_test.go index e6ea4876e5..976ff37493 100644 --- a/models/auth/access_token_test.go +++ b/models/auth/access_token_test.go @@ -131,3 +131,28 @@ func TestDeleteAccessTokenByID(t *testing.T) { require.Error(t, err) assert.True(t, auth_model.IsErrAccessTokenNotExist(err)) } + +func TestRegenerateAccessTokenByID(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c") + require.NoError(t, err) + + newToken, err := auth_model.RegenerateAccessTokenByID(db.DefaultContext, token.ID, 1) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: token.ID, UID: token.UID, TokenHash: token.TokenHash}) + newToken = &auth_model.AccessToken{ + ID: newToken.ID, + UID: newToken.UID, + TokenHash: newToken.TokenHash, + } + unittest.AssertExistsAndLoadBean(t, newToken) + + // Token has been recreated, new salt and hash, but should retain the same ID, UID, Name and Scope + assert.Equal(t, token.ID, newToken.ID) + assert.NotEqual(t, token.TokenHash, newToken.TokenHash) + assert.NotEqual(t, token.TokenSalt, newToken.TokenSalt) + assert.Equal(t, token.UID, newToken.UID) + assert.Equal(t, token.Name, newToken.Name) + assert.Equal(t, token.Scope, newToken.Scope) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8459d72f9e..0cf79445d3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -943,6 +943,10 @@ delete_token = Delete access_token_deletion = Delete access token access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. This cannot be undone. Continue? delete_token_success = The token has been deleted. Applications using it no longer have access to your account. +regenerate_token = Regenerate +access_token_regeneration = Regenerate access token +access_token_regeneration_desc = Regenerating a token will revoke access to your account for applications using it. This cannot be undone. Continue? +regenerate_token_success = The token has been regenerated. Applications that use it no longer have access to your account and must be updated with the new token. repo_and_org_access = Repository and Organization Access permissions_public_only = Public only permissions_access_all = All (public, private, and limited) diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 24ebf9b922..4dfd859a44 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -10,6 +10,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" @@ -87,6 +88,23 @@ func DeleteApplication(ctx *context.Context) { ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications") } +// RegenerateApplication response for regenerating user access token +func RegenerateApplication(ctx *context.Context) { + if t, err := auth_model.RegenerateAccessTokenByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID); err != nil { + if auth_model.IsErrAccessTokenNotExist(err) { + ctx.Flash.Error(ctx.Tr("error.not_found")) + } else { + ctx.Flash.Error(ctx.Tr("error.server_internal")) + log.Error("DeleteAccessTokenByID", err) + } + } else { + ctx.Flash.Success(ctx.Tr("settings.regenerate_token_success")) + ctx.Flash.Info(t.Token) + } + + ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications") +} + func loadApplicationsData(ctx *context.Context) { ctx.Data["AccessTokenScopePublicOnly"] = auth_model.AccessTokenScopePublicOnly tokens, err := db.Find[auth_model.AccessToken](ctx, auth_model.ListAccessTokensOptions{UserID: ctx.Doer.ID}) diff --git a/routers/web/web.go b/routers/web/web.go index 128d072741..9706319b3b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -586,6 +586,7 @@ func registerRoutes(m *web.Route) { m.Combo("").Get(user_setting.Applications). Post(web.Bind(forms.NewAccessTokenForm{}), user_setting.ApplicationsPost) m.Post("/delete", user_setting.DeleteApplication) + m.Post("/regenerate", user_setting.RegenerateApplication) }) m.Combo("/keys").Get(user_setting.Keys). diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 2aeabc6903..38ede95a77 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -40,6 +40,10 @@
+
+ + diff --git a/templates/base/modal_actions_confirm.tmpl b/templates/base/modal_actions_confirm.tmpl index c44320deff..8c4e346088 100644 --- a/templates/base/modal_actions_confirm.tmpl +++ b/templates/base/modal_actions_confirm.tmpl @@ -1,7 +1,6 @@ {{/* Two buttons (negative, positive): * ModalButtonTypes: "yes" (default) or "confirm" -* ModalButtonColors: "primary" (default) / "blue" / "yellow" * ModalButtonCancelText * ModalButtonOkText @@ -23,13 +22,7 @@ The ".ok.button" and ".cancel.button" selectors are also used by Fomantic Modal {{if .ModalButtonCancelText}}{{$textNegitive = .ModalButtonCancelText}}{{end}} {{if .ModalButtonOkText}}{{$textPositive = .ModalButtonOkText}}{{end}} - {{$stylePositive := "primary"}} - {{if eq .ModalButtonColors "blue"}} - {{$stylePositive = "blue"}} - {{else if eq .ModalButtonColors "yellow"}} - {{$stylePositive = "yellow"}} - {{end}} - + {{end}} diff --git a/templates/devtest/fomantic-modal.tmpl b/templates/devtest/fomantic-modal.tmpl index 5cd36721a7..5b94afc4f1 100644 --- a/templates/devtest/fomantic-modal.tmpl +++ b/templates/devtest/fomantic-modal.tmpl @@ -54,18 +54,6 @@ {{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}} - - - - diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 38ede95a77..9a806d0665 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -122,7 +122,7 @@

{{ctx.Locale.Tr "settings.access_token_deletion_desc"}}

- {{template "base/modal_actions_confirm" (dict "ModalButtonColors" "primary")}} + {{template "base/modal_actions_confirm"}} {{template "user/settings/layout_footer" .}} From 8fce7fbec7b77f61e8109b25b1f96a3864254abe Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Sun, 9 Mar 2025 15:05:24 +0000 Subject: [PATCH 70/79] fix(ui): use usual and consistent size for project icons of 16 (#7175) 18px is not a size used for icons often. In these cases it was also inconsistent with most of the UI. Affected areas: * `filter_list` - dropdown, filtering repo issues by project * `filter_actions` - dropdown, mass-editing issues via issue list * `sidebar/projects` - dropdown in issue view * `new_form` - dropdown when creating issue. Projects must be enabled in repo So all areas are dropdowns. I think these were inconsistent because other dropdowns like Reviewers and Milestones use the usual icons of 16px. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7175 Reviewed-by: Gusted Co-authored-by: 0ko <0ko@noreply.codeberg.org> Co-committed-by: 0ko <0ko@noreply.codeberg.org> --- templates/repo/issue/filter_actions.tmpl | 4 ++-- templates/repo/issue/filter_list.tmpl | 4 ++-- templates/repo/issue/new_form.tmpl | 6 +++--- templates/repo/issue/view_content/sidebar/projects.tmpl | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/repo/issue/filter_actions.tmpl b/templates/repo/issue/filter_actions.tmpl index 58b1ef8ecd..60237f225d 100644 --- a/templates/repo/issue/filter_actions.tmpl +++ b/templates/repo/issue/filter_actions.tmpl @@ -85,7 +85,7 @@ {{range .OpenProjects}}
- {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}}
{{end}} {{end}} @@ -96,7 +96,7 @@ {{range .ClosedProjects}}
- {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}}
{{end}} {{end}} diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl index f59c6d67ce..ae50ac4c46 100644 --- a/templates/repo/issue/filter_list.tmpl +++ b/templates/repo/issue/filter_list.tmpl @@ -60,7 +60,7 @@ {{range .OpenProjects}} - {{svg .IconName 18 "tw-mr-2 tw-shrink-0"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2 tw-shrink-0"}}{{.Title}} {{end}} {{end}} @@ -71,7 +71,7 @@ {{range .ClosedProjects}} - {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}} {{end}} {{end}} diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index c2cf4ee7a7..2c7807206e 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -110,7 +110,7 @@ {{range .OpenProjects}} - {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}} {{end}} {{end}} @@ -121,7 +121,7 @@ {{range .ClosedProjects}} - {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}} {{end}} {{end}} @@ -133,7 +133,7 @@ diff --git a/templates/repo/issue/view_content/sidebar/projects.tmpl b/templates/repo/issue/view_content/sidebar/projects.tmpl index 91d75f3bd9..3fd58b3340 100644 --- a/templates/repo/issue/view_content/sidebar/projects.tmpl +++ b/templates/repo/issue/view_content/sidebar/projects.tmpl @@ -25,7 +25,7 @@ {{range .OpenProjects}} - {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}} {{end}} {{end}} @@ -36,7 +36,7 @@ {{range .ClosedProjects}} - {{svg .IconName 18 "tw-mr-2"}}{{.Title}} + {{svg .IconName 16 "tw-mr-2"}}{{.Title}} {{end}} {{end}} @@ -47,7 +47,7 @@ From 140b28b60d6662e41b41cf8bdf976af70af1f312 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Sun, 9 Mar 2025 15:05:34 +0000 Subject: [PATCH 71/79] chore(ui): improve svg icon margin consistency (#7172) * followup to https://codeberg.org/forgejo/forgejo/pulls/7031#issuecomment-2868277: no visible changes * followup to https://codeberg.org/forgejo/forgejo/pulls/6963: apply a small margin to the icon Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7172 Reviewed-by: Gusted Co-authored-by: 0ko <0ko@noreply.codeberg.org> Co-committed-by: 0ko <0ko@noreply.codeberg.org> --- templates/base/head_navbar.tmpl | 8 ++++---- templates/user/settings/applications.tmpl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 0825dbf7bc..0c13f9e844 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -194,13 +194,13 @@ {{else}} {{if .ShowRegistrationButton}} - {{svg "octicon-person"}} - {{ctx.Locale.Tr "register"}} + {{svg "octicon-person" 16 "tw-mr-1"}} + {{ctx.Locale.Tr "register"}} {{end}} - {{svg "octicon-sign-in"}} - {{ctx.Locale.Tr "sign_in"}} + {{svg "octicon-sign-in" 16 "tw-mr-1"}} + {{ctx.Locale.Tr "sign_in"}} {{end}} diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 9a806d0665..a5912c9e0f 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -105,7 +105,7 @@